From f01fb21da518ef6eb455e33e100561c543c95a1c Mon Sep 17 00:00:00 2001 From: yellows8 Date: Sat, 12 Sep 2020 18:53:30 -0400 Subject: [PATCH] Added uart. --- nx/include/switch.h | 1 + nx/include/switch/services/uart.h | 243 ++++++++++++++++++++++++++++ nx/source/services/uart.c | 258 ++++++++++++++++++++++++++++++ 3 files changed, 502 insertions(+) create mode 100644 nx/include/switch/services/uart.h create mode 100644 nx/source/services/uart.c diff --git a/nx/include/switch.h b/nx/include/switch.h index 01c743b1..b0e5cfeb 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -60,6 +60,7 @@ extern "C" { #include "switch/services/lbl.h" #include "switch/services/i2c.h" #include "switch/services/gpio.h" +#include "switch/services/uart.h" #include "switch/services/bpc.h" #include "switch/services/pcv.h" #include "switch/services/clkrst.h" diff --git a/nx/include/switch/services/uart.h b/nx/include/switch/services/uart.h new file mode 100644 index 00000000..dccee01c --- /dev/null +++ b/nx/include/switch/services/uart.h @@ -0,0 +1,243 @@ +/** + * @file uart.h + * @brief UART service IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../kernel/event.h" +#include "../sf/service.h" + +/// UartPort +typedef enum { + UartPort_Bluetooth = 1, ///< Bluetooth + UartPort_JoyConR = 2, ///< Joy-Con(R) + UartPort_JoyConL = 3, ///< Joy-Con(L) + UartPort_MCU = 4, ///< MCU +} UartPort; + +/// UartPortForDev +typedef enum { + UartPortForDev_JoyConR = 1, ///< Joy-Con(R) + UartPortForDev_JoyConL = 2, ///< Joy-Con(L) + UartPortForDev_Bluetooth = 3, ///< Bluetooth +} UartPortForDev; + +/// FlowControlMode +typedef enum { + UartFlowControlMode_None = 0, ///< None + UartFlowControlMode_Hardware = 1, ///< Hardware +} UartFlowControlMode; + +/// PortEventType +typedef enum { + UartPortEventType_SendBufferEmpty = 0, ///< SendBufferEmpty + UartPortEventType_SendBufferReady = 1, ///< SendBufferReady + UartPortEventType_ReceiveBufferReady = 2, ///< ReceiveBufferReady + UartPortEventType_ReceiveEnd = 3, ///< ReceiveEnd +} UartPortEventType; + +/// PortSession +typedef struct { + Service s; ///< IPortSession +} UartPortSession; + +/// Initialize uart. +Result uartInitialize(void); + +/// Exit uart. +void uartExit(void); + +/// Gets the Service object for the actual uart service session. +Service* uartGetServiceSession(void); + +/** + * @brief HasPort + * @param[in] port \ref UartPort + * @param[out] out Output success flag. + */ +Result uartHasPort(UartPort port, bool *out); + +/** + * @brief HasPortForDev + * @param[in] port \ref UartPortForDev + * @param[out] out Output success flag. + */ +Result uartHasPortForDev(UartPortForDev port, bool *out); + +/** + * @brief IsSupportedBaudRate + * @param[in] port \ref UartPort + * @param[in] baud_rate BaudRate + * @param[out] out Output success flag. + */ +Result uartIsSupportedBaudRate(UartPort port, u32 baud_rate, bool *out); + +/** + * @brief IsSupportedBaudRateForDev + * @param[in] port \ref UartPortForDev + * @param[in] baud_rate BaudRate + * @param[out] out Output success flag. + */ +Result uartIsSupportedBaudRateForDev(UartPortForDev port, u32 baud_rate, bool *out); + +/** + * @brief IsSupportedFlowControlMode + * @param[in] port \ref UartPort + * @param[in] flow_control_mode \ref UartFlowControlMode + * @param[out] out Output success flag. + */ +Result uartIsSupportedFlowControlMode(UartPort port, UartFlowControlMode flow_control_mode, bool *out); + +/** + * @brief IsSupportedFlowControlModeForDev + * @param[in] port \ref UartPortForDev + * @param[in] flow_control_mode \ref UartFlowControlMode + * @param[out] out Output success flag. + */ +Result uartIsSupportedFlowControlModeForDev(UartPortForDev port, UartFlowControlMode flow_control_mode, bool *out); + +/** + * @brief Creates an \ref UartPortSession. + * @note Use \ref uartPortSessionOpenPort or \ref uartPortSessionOpenPortForDev before using any other cmds. + * @param[out] s \ref UartPortSession + */ +Result uartCreatePortSession(UartPortSession *s); + +/** + * @brief IsSupportedPortEvent + * @param[in] port \ref UartPort + * @param[in] port_event_type \ref UartPortEventType + * @param[out] out Output success flag. + */ +Result uartIsSupportedPortEvent(UartPort port, UartPortEventType port_event_type, bool *out); + +/** + * @brief IsSupportedPortEventForDev + * @param[in] port \ref UartPortForDev + * @param[in] port_event_type \ref UartPortEventType + * @param[out] out Output success flag. + */ +Result uartIsSupportedPortEventForDev(UartPortForDev port, UartPortEventType port_event_type, bool *out); + +/** + * @brief IsSupportedDeviceVariation + * @note Only available on [7.0.0+]. + * @param[in] port \ref UartPort + * @param[in] device_variation DeviceVariation + * @param[out] out Output success flag. + */ +Result uartIsSupportedDeviceVariation(UartPort port, u32 device_variation, bool *out); + +/** + * @brief IsSupportedDeviceVariationForDev + * @note Only available on [7.0.0+]. + * @param[in] port \ref UartPortForDev + * @param[in] device_variation DeviceVariation + * @param[out] out Output success flag. + */ +Result uartIsSupportedDeviceVariationForDev(UartPortForDev port, u32 device_variation, bool *out); + +///@name IPortSession +///@{ + +/** + * @brief Close an \ref UartPortSession. + * @param s \ref UartPortSession + */ +void uartPortSessionClose(UartPortSession* s); + +/** + * @brief OpenPort + * @note This is not usable when the specified \ref UartPort is already being used. + * @param s \ref UartPortSession + * @param[out] out Output success flag. + * @param[in] port \ref UartPort + * @param[in] baud_rate BaudRate + * @param[in] flow_control_mode \ref UartFlowControlMode + * @param[in] device_variation [7.0.0+] DeviceVariation + * @param[in] is_invert_tx [6.0.0+] IsInvertTx + * @param[in] is_invert_rx [6.0.0+] IsInvertRx + * @param[in] is_invert_rts [6.0.0+] IsInvertRts + * @param[in] is_invert_cts [6.0.0+] IsInvertCts + * @param[in] send_buffer Send buffer, must be 0x1000-byte aligned. + * @param[in] send_buffer_length Send buffer size, must be 0x1000-byte aligned. + * @param[in] receive_buffer Receive buffer, must be 0x1000-byte aligned. + * @param[in] receive_buffer_length Receive buffer size, must be 0x1000-byte aligned. + */ +Result uartPortSessionOpenPort(UartPortSession* s, bool *out, UartPort port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, void* send_buffer, u64 send_buffer_length, void* receive_buffer, u64 receive_buffer_length); + +/** + * @brief OpenPortForDev + * @note See the notes for \ref uartPortSessionOpenPort. + * @param s \ref UartPortSession + * @param[out] out Output success flag. + * @param[in] port \ref UartPortForDev + * @param[in] baud_rate BaudRate + * @param[in] flow_control_mode \ref UartFlowControlMode + * @param[in] device_variation [7.0.0+] DeviceVariation + * @param[in] is_invert_tx [6.0.0+] IsInvertTx + * @param[in] is_invert_rx [6.0.0+] IsInvertRx + * @param[in] is_invert_rts [6.0.0+] IsInvertRts + * @param[in] is_invert_cts [6.0.0+] IsInvertCts + * @param[in] send_buffer Send buffer, must be 0x1000-byte aligned. + * @param[in] send_buffer_length Send buffer size, must be 0x1000-byte aligned. + * @param[in] receive_buffer Receive buffer, must be 0x1000-byte aligned. + * @param[in] receive_buffer_length Receive buffer size, must be 0x1000-byte aligned. + */ +Result uartPortSessionOpenPortForDev(UartPortSession* s, bool *out, UartPortForDev port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, void* send_buffer, u64 send_buffer_length, void* receive_buffer, u64 receive_buffer_length); + +/** + * @brief GetWritableLength + * @param s \ref UartPortSession + * @param[out] out Output WritableLength. + */ +Result uartPortSessionGetWritableLength(UartPortSession* s, u64 *out); + +/** + * @brief Send + * @param s \ref UartPortSession + * @param[in] in_data Input data buffer. + * @param[in] size Input data buffer size. + * @param[out] out Output size. + */ +Result uartPortSessionSend(UartPortSession* s, const void* in_data, size_t size, u64 *out); + +/** + * @brief GetReadableLength + * @param s \ref UartPortSession + * @param[out] out Output ReadableLength. + */ +Result uartPortSessionGetReadableLength(UartPortSession* s, u64 *out); + +/** + * @brief Receive + * @param s \ref UartPortSession + * @param[out] out_data Output data buffer. + * @param[in] size Output data buffer size. + * @param[out] out Output size. + */ +Result uartPortSessionReceive(UartPortSession* s, void* out_data, size_t size, u64 *out); + +/** + * @brief BindPortEvent + * @note The Event must be closed by the user after using \ref uartPortSessionUnbindPortEvent. + * @param s \ref UartPortSession + * @param[in] port_event_type \ref UartPortEventType + * @param[in] threshold Threshold + * @param[out] out Output success flag. + * @param[out] out_event Output Event with autoclear=false. + */ +Result uartPortSessionBindPortEvent(UartPortSession* s, UartPortEventType port_event_type, s64 threshold, bool *out, Event *out_event); + +/** + * @brief UnbindPortEvent + * @param s \ref UartPortSession + * @param[in] port_event_type \ref UartPortEventType + * @param[out] out Output success flag. + */ +Result uartPortSessionUnbindPortEvent(UartPortSession* s, UartPortEventType port_event_type, bool *out); + +///@} + diff --git a/nx/source/services/uart.c b/nx/source/services/uart.c new file mode 100644 index 00000000..62ef3aae --- /dev/null +++ b/nx/source/services/uart.c @@ -0,0 +1,258 @@ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include "service_guard.h" +#include "runtime/hosversion.h" +#include "kernel/tmem.h" +#include "services/uart.h" + +static Service g_uartSrv; + +NX_GENERATE_SERVICE_GUARD(uart); + +Result _uartInitialize(void) { + return smGetService(&g_uartSrv, "uart"); +} + +void _uartCleanup(void) { + serviceClose(&g_uartSrv); +} + +Service* uartGetServiceSession(void) { + return &g_uartSrv; +} + +static Result _uartCmdNoInOutU64(Service* srv, u64 *out, u32 cmd_id) { + return serviceDispatchOut(srv, cmd_id, *out); +} + +static Result _uartCmdInU32OutBool(Service* srv, u32 inval, bool *out, u32 cmd_id) { + u8 tmp=0; + Result rc = serviceDispatchInOut(srv, cmd_id, inval, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +static Result _uartCmdInTwoU32sOutBool(Service* srv, u32 inval0, u32 inval1, bool *out, u32 cmd_id) { + const struct { + u32 inval0; + u32 inval1; + } in = { inval0, inval1 }; + + u8 tmp=0; + Result rc = serviceDispatchInOut(srv, cmd_id, in, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +Result uartHasPort(UartPort port, bool *out) { + return _uartCmdInU32OutBool(&g_uartSrv, port, out, 0); +} + +Result uartHasPortForDev(UartPortForDev port, bool *out) { + return _uartCmdInU32OutBool(&g_uartSrv, port, out, 1); +} + +Result uartIsSupportedBaudRate(UartPort port, u32 baud_rate, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, baud_rate, out, 2); +} + +Result uartIsSupportedBaudRateForDev(UartPortForDev port, u32 baud_rate, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, baud_rate, out, 3); +} + +Result uartIsSupportedFlowControlMode(UartPort port, UartFlowControlMode flow_control_mode, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, flow_control_mode, out, 4); +} + +Result uartIsSupportedFlowControlModeForDev(UartPortForDev port, UartFlowControlMode flow_control_mode, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, flow_control_mode, out, 5); +} + +Result uartCreatePortSession(UartPortSession *s) { + return serviceDispatch(&g_uartSrv, 6, + .out_num_objects = 1, + .out_objects = &s->s, + ); +} + +Result uartIsSupportedPortEvent(UartPort port, UartPortEventType port_event_type, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, port_event_type, out, 7); +} + +Result uartIsSupportedPortEventForDev(UartPortForDev port, UartPortEventType port_event_type, bool *out) { + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, port_event_type, out, 8); +} + +Result uartIsSupportedDeviceVariation(UartPort port, u32 device_variation, bool *out) { + if (hosversionBefore(7,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, device_variation, out, 9); +} + +Result uartIsSupportedDeviceVariationForDev(UartPortForDev port, u32 device_variation, bool *out) { + if (hosversionBefore(7,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _uartCmdInTwoU32sOutBool(&g_uartSrv, port, device_variation, out, 10); +} + +// IPortSession + +void uartPortSessionClose(UartPortSession* s) { + serviceClose(&s->s); +} + +/// pre-6.0.0 +static Result _uartPortSessionOpenPort_v1(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length, u32 cmd_id) { + const struct { + u32 port; + u32 baud_rate; + u32 flow_control_mode; + u32 pad; + u64 send_buffer_length; + u64 receive_buffer_length; + } in = { port, baud_rate, flow_control_mode, 0, send_buffer_length, receive_buffer_length }; + + u8 tmp=0; + Result rc = serviceDispatchInOut(&s->s, cmd_id, in, tmp, + .in_num_handles = 2, + .in_handles = { send_handle, receive_handle }, + ); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +/// 6.x +static Result _uartPortSessionOpenPort_v6(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length, u32 cmd_id) { + const struct { + u8 is_invert_tx; + u8 is_invert_rx; + u8 is_invert_rts; + u8 is_invert_cts; + u32 port; + u32 baud_rate; + u32 flow_control_mode; + u64 send_buffer_length; + u64 receive_buffer_length; + } in = { is_invert_tx!=0, is_invert_rx!=0, is_invert_rts!=0, is_invert_cts!=0, port, baud_rate, flow_control_mode, send_buffer_length, receive_buffer_length }; + + u8 tmp=0; + Result rc = serviceDispatchInOut(&s->s, cmd_id, in, tmp, + .in_num_handles = 2, + .in_handles = { send_handle, receive_handle }, + ); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +/// [7.0.0+] +static Result _uartPortSessionOpenPort_v7(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length, u32 cmd_id) { + const struct { + u8 is_invert_tx; + u8 is_invert_rx; + u8 is_invert_rts; + u8 is_invert_cts; + u32 port; + u32 baud_rate; + u32 flow_control_mode; + u32 device_variation; + u32 pad; + u64 send_buffer_length; + u64 receive_buffer_length; + } in = { is_invert_tx!=0, is_invert_rx!=0, is_invert_rts!=0, is_invert_cts!=0, port, baud_rate, flow_control_mode, device_variation, 0, send_buffer_length, receive_buffer_length }; + + u8 tmp=0; + Result rc = serviceDispatchInOut(&s->s, cmd_id, in, tmp, + .in_num_handles = 2, + .in_handles = { send_handle, receive_handle }, + ); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +static Result _uartPortSessionOpenPort(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length, u32 cmd_id) { + if (hosversionBefore(6,0,0)) + return _uartPortSessionOpenPort_v1(s, out, port, baud_rate, flow_control_mode, send_handle, receive_handle, send_buffer_length, receive_buffer_length, cmd_id); + else if (hosversionBefore(7,0,0)) // 6.x + return _uartPortSessionOpenPort_v6(s, out, port, baud_rate, flow_control_mode, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length, cmd_id); + else // [7.0.0+] + return _uartPortSessionOpenPort_v7(s, out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length, cmd_id); +} + +Result uartPortSessionOpenPort(UartPortSession* s, bool *out, UartPort port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, void* send_buffer, u64 send_buffer_length, void* receive_buffer, u64 receive_buffer_length) { + Result rc=0; + TransferMemory send_tmem={0}; + TransferMemory receive_tmem={0}; + + rc = tmemCreateFromMemory(&send_tmem, send_buffer, send_buffer_length, Perm_R | Perm_W); + if (R_SUCCEEDED(rc)) rc = tmemCreateFromMemory(&receive_tmem, receive_buffer, send_buffer_length, Perm_R | Perm_W); + + if (R_SUCCEEDED(rc)) rc = _uartPortSessionOpenPort(s, out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_tmem.handle, receive_tmem.handle, send_buffer_length, receive_buffer_length, 0); + + tmemClose(&send_tmem); + tmemClose(&receive_tmem); + + return rc; +} + +Result uartPortSessionOpenPortForDev(UartPortSession* s, bool *out, UartPortForDev port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, void* send_buffer, u64 send_buffer_length, void* receive_buffer, u64 receive_buffer_length) { + Result rc=0; + TransferMemory send_tmem={0}; + TransferMemory receive_tmem={0}; + + rc = tmemCreateFromMemory(&send_tmem, send_buffer, send_buffer_length, Perm_R | Perm_W); + if (R_SUCCEEDED(rc)) rc = tmemCreateFromMemory(&receive_tmem, receive_buffer, send_buffer_length, Perm_R | Perm_W); + + if (R_SUCCEEDED(rc)) rc = _uartPortSessionOpenPort(s, out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_tmem.handle, receive_tmem.handle, send_buffer_length, receive_buffer_length, 1); + + tmemClose(&send_tmem); + tmemClose(&receive_tmem); + + return rc; +} + +Result uartPortSessionGetWritableLength(UartPortSession* s, u64 *out) { + return _uartCmdNoInOutU64(&s->s, out, 2); +} + +Result uartPortSessionSend(UartPortSession* s, const void* in_data, size_t size, u64 *out_size) { + return serviceDispatchOut(&s->s, 3, *out_size, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { in_data, size } }, + ); +} + +Result uartPortSessionGetReadableLength(UartPortSession* s, u64 *out) { + return _uartCmdNoInOutU64(&s->s, out, 4); +} + +Result uartPortSessionReceive(UartPortSession* s, void* out_data, size_t size, u64 *out_size) { + return serviceDispatchOut(&s->s, 5, *out_size, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { out_data, size } }, + ); +} + +Result uartPortSessionBindPortEvent(UartPortSession* s, UartPortEventType port_event_type, s64 threshold, bool *out, Event *out_event) { + const struct { + u32 port_event_type; + u32 pad; + s64 threshold; + } in = { port_event_type, 0, threshold }; + + u8 tmp=0; + Handle tmp_handle=0; + Result rc = serviceDispatchInOut(&s->s, 6, in, tmp, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &tmp_handle, + ); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + if (R_SUCCEEDED(rc) && out_event) eventLoadRemote(out_event, tmp_handle, false); + return rc; +} + +Result uartPortSessionUnbindPortEvent(UartPortSession* s, UartPortEventType port_event_type, bool *out) { + return _uartCmdInU32OutBool(&s->s, port_event_type, out, 7); +} +