libnx/nx/source/runtime/ringcon.c

625 lines
19 KiB
C

#include <string.h>
#include <malloc.h>
#include "runtime/ringcon.h"
#include "arm/counter.h"
static Result _ringconSetup(RingCon *c);
/**
* \file
* Functions and types for CRC checks.
*
* Generated on Fri Mar 13 12:10:23 2020
* by pycrc v0.9.2, https://pycrc.org
* using the configuration:
* - Width = 8
* - Poly = 0x8d
* - XorIn = 0x00
* - ReflectIn = False
* - XorOut = 0x00
* - ReflectOut = False
* - Algorithm = bit-by-bit-fast
*/
// Generated output from pycrc was slightly adjusted.
static u8 crc_update(u8 crc, const void *data, size_t data_len) {
const u8 *d = (const u8*)data;
u32 i;
bool bit;
u8 c;
while (data_len--) {
c = *d++;
for (i = 0x80; i > 0; i >>= 1) {
bit = crc & 0x80;
if (c & i) {
bit = !bit;
}
crc <<= 1;
if (bit) {
crc ^= 0x8d;
}
}
crc &= 0xff;
}
return crc & 0xff;
}
static void _ringconSetErrorFlag(RingCon *c, RingConErrorFlag flag, bool value) {
if (value)
c->error_flags |= BIT(flag);
else
c->error_flags &= ~BIT(flag);
}
Result ringconCreate(RingCon *c, HidControllerID id) {
Result rc=0;
bool handleflag=0;
HidbusBusType bus_type;
u32 type = hidGetControllerType(id);
u32 cmd = 0x00020101;
memset(c, 0, sizeof(*c));
c->workbuf_size = 0x1000;
c->workbuf = memalign(0x1000, c->workbuf_size);
if (c->workbuf == NULL)
rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
else
memset(c->workbuf, 0, c->workbuf_size);
if (R_SUCCEEDED(rc)) {
if (type & TYPE_JOYCON_LEFT)
bus_type = HidbusBusType_JoyLeftRail;
else if (type & (TYPE_JOYCON_RIGHT | TYPE_JOYCON_PAIR))
bus_type = HidbusBusType_JoyRightRail;
else
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
}
if (R_SUCCEEDED(rc)) rc = hidbusGetBusHandle(&c->handle, &handleflag, id, bus_type);
if (R_SUCCEEDED(rc) && !handleflag) rc = MAKERESULT(Module_Libnx, LibnxError_NotFound);
if (R_SUCCEEDED(rc)) rc = hidbusInitialize(c->handle);
if (R_SUCCEEDED(rc)) c->bus_initialized = true;
if (R_SUCCEEDED(rc)) rc = hidbusEnableExternalDevice(c->handle, true, 0x20);
if (R_SUCCEEDED(rc)) rc = hidbusEnableJoyPollingReceiveMode(c->handle, &cmd, sizeof(cmd), c->workbuf, c->workbuf_size, HidbusJoyPollingMode_JoyEnableSixAxisPollingData);
if (R_SUCCEEDED(rc)) rc = _ringconSetup(c);
if (R_FAILED(rc)) ringconClose(c);
return rc;
}
void ringconClose(RingCon *c) {
if (c->bus_initialized) {
c->bus_initialized = false;
// Official sw uses hidbusDisableJoyPollingReceiveMode here, but that's redundant since hidbusEnableExternalDevice with flag=false uses that automatically.
hidbusEnableExternalDevice(c->handle, false, 0x20);
hidbusFinalize(c->handle);
}
free(c->workbuf);
c->workbuf = 0;
}
static Result _ringconSetup(RingCon *c) {
Result rc=0, rc2=0;
u64 prev_tick=0;
u64 tick_freq = armGetSystemTickFreq(); // Used for 1-second timeout.
while (R_FAILED(rc2 = ringconCmdx00020105(c, &c->flag))) {
if (R_VALUE(rc2) != MAKERESULT(218, 7)) return rc;
prev_tick = armGetSystemTick();
while (R_FAILED(rc = ringconReadFwVersion(c, &c->fw_ver))) {
if (armGetSystemTick() - prev_tick >= tick_freq) return rc;
}
if (c->fw_ver.fw_main_ver > 0x1f) continue;
c->flag = 1;
break;
}
if (R_SUCCEEDED(rc2)) {
if (c->flag == 1) {
prev_tick = armGetSystemTick();
do {
rc2 = ringconReadFwVersion(c, &c->fw_ver);
} while (R_FAILED(rc2) && armGetSystemTick() - prev_tick < tick_freq);
prev_tick = armGetSystemTick();
while (R_FAILED(rc = ringconReadUnkCal(c, &c->unk_cal))) {
if (armGetSystemTick() - prev_tick >= tick_freq) {
return R_SUCCEEDED(rc2) ? rc : rc2;
}
}
if (R_FAILED(rc2)) return rc2;
}
}
s32 total_push_count = 0;
RingConDataValid data_valid = RingConDataValid_Ok;
rc = ringconReadTotalPushCount(c, &total_push_count, &data_valid);
if (R_SUCCEEDED(rc) && data_valid == RingConDataValid_Ok) c->total_push_count = total_push_count;
// Official sw has a success_flag, where the app just write rc=0 to state returns here, when it's not set. But it's not actually possible to reach this point with that flag being clear, so we won't impl that.
if (c->flag != 1) {
_ringconSetErrorFlag(c, RingConErrorFlag_BadFlag, true);
return MAKERESULT(Module_Libnx, LibnxError_IoError);
}
rc = ringconReadId(c, &c->id_l, &c->id_h);
if (R_FAILED(rc)) return rc;
for (u32 i=0; i<2; i++) {
rc = ringconReadManuCal(c, &c->manu_cal);
if (R_SUCCEEDED(rc))
break;
else if (i==1)
return rc;
}
for (u32 i=0; i<2; i++) {
rc = ringconReadUserCal(c, &c->user_cal);
if (R_SUCCEEDED(rc))
break;
else if (i==1)
return rc;
}
s16 manu_os_max = c->manu_cal.os_max;
s16 manu_hk_max = c->manu_cal.hk_max;
s16 manu_zero_min = c->manu_cal.zero_min;
if (manu_os_max <= manu_hk_max || manu_zero_min <= manu_hk_max || c->manu_cal.zero_max <= manu_zero_min || manu_os_max <= manu_zero_min) {
_ringconSetErrorFlag(c, RingConErrorFlag_BadManuCal, true);
return MAKERESULT(218, 7);
}
s16 user_os_max = c->user_cal.os_max;
if (c->user_cal.data_valid == RingConDataValid_CRC || (user_os_max != RINGCON_CAL_MAGIC && user_os_max <= c->user_cal.hk_max)) {
_ringconSetErrorFlag(c, RingConErrorFlag_BadUserCal, true);
return MAKERESULT(Module_Libnx, LibnxError_IoError);
}
return 0;
}
Result ringconUpdateUserCal(RingCon *c, RingConUserCal cal) {
Result rc=0;
RingConUserCal tmp_cal={0};
_ringconSetErrorFlag(c, RingConErrorFlag_BadUserCalUpdate, false);
for (u32 i=0; i<2; i++) {
rc = ringconWriteUserCal(c, cal);
if (R_SUCCEEDED(rc)) {
rc = ringconReadUserCal(c, &tmp_cal);
if (R_SUCCEEDED(rc) && tmp_cal.data_valid == RingConDataValid_Ok && tmp_cal.os_max == cal.os_max && tmp_cal.hk_max == cal.hk_max && tmp_cal.zero == cal.zero) return 0;
}
}
if (R_FAILED(rc)) return rc;
_ringconSetErrorFlag(c, RingConErrorFlag_BadUserCalUpdate, true);
return MAKERESULT(Module_Libnx, LibnxError_IoError);
}
Result ringconReadFwVersion(RingCon *c, RingConFwVersion *out) {
Result rc=0;
u32 cmd = 0x00020000;
u64 out_size=0;
struct {
u8 status;
u8 pad[0x3];
u8 fw_sub_ver;
u8 fw_main_ver;
u8 pad2[0x2];
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
out->fw_main_ver = reply.fw_main_ver;
out->fw_sub_ver = reply.fw_sub_ver;
}
return rc;
}
Result ringconReadId(RingCon *c, u64 *id_l, u64 *id_h) {
Result rc=0;
u32 cmd = 0x00020100;
u64 out_size=0;
struct {
u8 status;
u8 pad[0x3];
struct {
struct {
u32 data_x0;
u16 data_x4;
} PACKED id_l;
struct {
u32 data_x0;
u16 data_x4;
} PACKED id_h;
} id;
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
*id_l = reply.id.id_l.data_x0 | ((u64)reply.id.id_l.data_x4<<32);
*id_h = reply.id.id_h.data_x0 | ((u64)reply.id.id_h.data_x4<<32);
}
return rc;
}
Result ringconGetPollingData(RingCon *c, RingConPollingData *out, s32 count, s32 *total_out) {
Result rc=0;
HidbusJoyPollingReceivedData recv_data[0x9];
rc = hidbusGetJoyPollingReceivedData(c->handle, recv_data, 0x9);
if (R_SUCCEEDED(rc)) {
u64 tmp = recv_data[0].timestamp - c->polling_last_timestamp;
if (tmp > 0x9) tmp = 0x9;
if (count > tmp) count = tmp;
c->polling_last_timestamp = recv_data[0].timestamp;
for (s32 i=0; i<count; i++) {
struct {
u8 status;
u8 pad[0x3];
s16 data;
u8 pad2[0x2];
} *reply = (void*)recv_data[i].data;
if (recv_data[i].size != sizeof(*reply) || reply->status != 0) return MAKERESULT(218, 7);
out[i].data = reply->data;
out[i].timestamp = recv_data[i].timestamp;
}
}
if (R_SUCCEEDED(rc) && count) *total_out = count;
return rc;
}
Result ringconCmdx00020105(RingCon *c, u32 *out) {
Result rc=0;
u32 cmd = 0x00020105;
u64 out_size=0;
struct {
u8 status;
u8 pad[0x3];
u8 data;
u8 pad2[0x3];
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) *out = reply.data;
return rc;
}
Result ringconReadManuCal(RingCon *c, RingConManuCal *out) {
Result rc=0;
u32 cmd=0;
u64 out_size=0;
RingConFwVersion ver={0};
rc = ringconReadFwVersion(c, &ver);
if (R_FAILED(rc)) return rc;
if (ver.fw_main_ver >= 0x20) {
struct {
u8 status;
u8 pad[0x3];
s16 os_max;
u16 pad1;
s16 hk_max;
u16 pad2;
s16 zero_min;
u16 pad3;
s16 zero_max;
u16 pad4;
} reply;
cmd = 0x00020A04;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
out->os_max = reply.os_max;
out->hk_max = reply.hk_max;
out->zero_min = reply.zero_min;
out->zero_max = reply.zero_max;
}
}
else {
struct {
u8 status;
u8 pad[0x3];
s16 data;
u8 pad2[0x2];
} reply;
if (R_SUCCEEDED(rc)) {
cmd = 0x00020104;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) out->os_max = reply.data;
}
if (R_SUCCEEDED(rc)) {
cmd = 0x00020204;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) out->hk_max = reply.data;
}
if (R_SUCCEEDED(rc)) {
cmd = 0x00020404;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) out->zero_max = reply.data;
}
if (R_SUCCEEDED(rc)) {
cmd = 0x00020304;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) out->zero_min = reply.data;
}
}
return rc;
}
Result ringconReadUnkCal(RingCon *c, s16 *out) {
Result rc=0;
u32 cmd = 0x00020504;
u64 out_size=0;
RingConManuCal cal={0};
struct {
u8 status;
u8 pad[0x3];
s16 data;
u8 pad2[0x2];
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_FAILED(rc)) return rc;
rc = ringconReadManuCal(c, &cal);
if (R_SUCCEEDED(rc)) {
s16 tmp = cal.hk_max - cal.os_max;
if (tmp < 0) tmp++;
*out = reply.data + (((u16)tmp)>>1);
}
return 0; // Official sw ignores error from the above ringconReadManuCal call, besides the above block.
}
static Result _ringconReadUserCalOld(RingCon *c, u32 cmd, s16 *out, RingConDataValid *data_valid) {
Result rc=0;
u64 out_size=0;
struct {
u8 status;
u8 pad[0x3];
s16 data;
u8 crc;
u8 pad2;
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
if (crc_update(0, &reply.data, sizeof(reply.data)) != reply.crc) *data_valid = RingConDataValid_CRC;
else {
*data_valid = RingConDataValid_Ok;
*out = reply.data;
}
}
return rc;
}
Result ringconReadUserCal(RingCon *c, RingConUserCal *out) {
Result rc=0;
u32 cmd = 0x00021A04;
u64 out_size=0;
RingConFwVersion ver={0};
out->data_valid = RingConDataValid_Ok;
rc = ringconReadFwVersion(c, &ver);
if (R_FAILED(rc)) return rc;
if (ver.fw_main_ver >= 0x20) {
struct {
u8 status;
u8 pad[0x3];
s16 os_max;
u8 os_max_crc;
u8 pad1;
s16 hk_max;
u8 hk_max_crc;
u8 pad2;
s16 zero;
u8 zero_crc;
u8 pad4;
u8 unused[0x4];
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
if ((crc_update(0, &reply.os_max, sizeof(reply.os_max)) != reply.os_max_crc) || (crc_update(0, &reply.hk_max, sizeof(reply.hk_max)) != reply.hk_max_crc) || (crc_update(0, &reply.zero, sizeof(reply.zero)) != reply.zero_crc))
out->data_valid = RingConDataValid_CRC;
else {
out->data_valid = RingConDataValid_Ok;
out->os_max = reply.os_max;
out->hk_max = reply.hk_max;
out->zero = reply.zero;
}
}
}
else {
// Official sw doesn't check the data_valid output from these.
rc = _ringconReadUserCalOld(c, 0x00021104 , &out->os_max, &out->data_valid);
if (R_SUCCEEDED(rc)) rc = _ringconReadUserCalOld(c, 0x00021204, &out->hk_max, &out->data_valid);
if (R_SUCCEEDED(rc)) rc = _ringconReadUserCalOld(c, 0x00021304, &out->zero, &out->data_valid);
}
if (R_SUCCEEDED(rc)) {
if (out->os_max == RINGCON_CAL_MAGIC || out->hk_max == RINGCON_CAL_MAGIC || out->zero == RINGCON_CAL_MAGIC)
out->data_valid = RingConDataValid_Cal;
}
return rc;
}
static Result _ringconGet3ByteOut(RingCon *c, u32 cmd, s32 *out, RingConDataValid *data_valid) {
Result rc=0;
u64 out_size=0;
u8 data[0x4]={0};
struct {
u8 status;
u8 pad[0x3];
u8 data[0x3];
u8 crc;
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != sizeof(reply) || reply.status != 0)) rc = MAKERESULT(218, 7);
if (R_SUCCEEDED(rc)) {
memcpy(data, reply.data, sizeof(reply.data));
if (crc_update(0, data, sizeof(data)) != reply.crc) *data_valid = RingConDataValid_CRC; // Official sw has this field value inverted with this func, but whatever.
else {
*data_valid = RingConDataValid_Ok;
*out = data[0x0] | (data[0x1]<<8) | (data[0x2]<<16);
}
}
return rc;
}
Result ringconReadRepCount(RingCon *c, s32 *out, RingConDataValid *data_valid) {
return _ringconGet3ByteOut(c, 0x00023104, out, data_valid);
}
Result ringconReadTotalPushCount(RingCon *c, s32 *out, RingConDataValid *data_valid) {
return _ringconGet3ByteOut(c, 0x00023204, out, data_valid);
}
Result ringconResetRepCount(RingCon *c) {
Result rc=0;
u64 cmd = 0x04013104;
u64 out_size=0;
struct {
u8 status;
u8 pad[0x3];
u8 unused[0x4];
} reply;
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && (out_size != 0x4 || reply.status != 0)) rc = MAKERESULT(218, 7); // Official sw uses value 0x4 for the out_size check, instead of the usual sizeof(reply) value.
return rc;
}
Result ringconWriteUserCal(RingCon *c, RingConUserCal cal) {
Result rc=0;
u64 out_size=0;
RingConFwVersion ver={0};
struct {
u32 cmd;
struct {
s16 data;
u8 crc;
u8 pad;
} os_max;
struct {
s16 data;
u8 crc;
u8 pad;
} hk_max;
struct {
s16 data;
u8 crc;
u8 pad;
} zero;
u8 unused[0x4];
} cmd = {.cmd = 0x10011A04};
struct {
u8 status;
u8 pad[0x3];
} reply;
cmd.os_max.data = cal.os_max;
cmd.os_max.crc = crc_update(0, &cmd.os_max.data, sizeof(cmd.os_max.data));
cmd.hk_max.data = cal.hk_max;
cmd.hk_max.crc = crc_update(0, &cmd.hk_max.data, sizeof(cmd.hk_max.data));
cmd.zero.data = cal.zero;
cmd.zero.crc = crc_update(0, &cmd.zero.data, sizeof(cmd.zero.data));
rc = ringconReadFwVersion(c, &ver);
if (R_FAILED(rc)) return rc;
// Official sw doesn't check the output_size for these.
if (ver.fw_main_ver >= 0x20) {
rc = hidbusSendAndReceive(c->handle, &cmd, sizeof(cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7);
}
else {
struct {
u32 cmd;
u8 data[0x4];
} old_cmd;
if (R_SUCCEEDED(rc)) {
old_cmd.cmd = 0x04011104;
memcpy(old_cmd.data, &cmd.os_max, sizeof(cmd.os_max));
rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7);
}
if (R_SUCCEEDED(rc)) {
old_cmd.cmd = 0x04011204;
memcpy(old_cmd.data, &cmd.hk_max, sizeof(cmd.hk_max));
rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7);
}
if (R_SUCCEEDED(rc)) {
old_cmd.cmd = 0x04011304;
memcpy(old_cmd.data, &cmd.zero, sizeof(cmd.zero));
rc = hidbusSendAndReceive(c->handle, &old_cmd, sizeof(old_cmd), &reply, sizeof(reply), &out_size);
if (R_SUCCEEDED(rc) && reply.status != 0) rc = MAKERESULT(218, 7);
}
}
return rc;
}