From f1ba199d1cce5b9dc8efb21feb04a17183f5e060 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 29 Nov 2018 17:36:31 -0500 Subject: [PATCH] Initial usbhs support. --- nx/include/switch.h | 1 + nx/include/switch/services/usbhs.h | 128 +++++++++++++ nx/source/services/usbhs.c | 293 +++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 nx/include/switch/services/usbhs.h create mode 100644 nx/source/services/usbhs.c diff --git a/nx/include/switch.h b/nx/include/switch.h index f6918b38..33f25f84 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -62,6 +62,7 @@ extern "C" { #include "switch/services/time.h" #include "switch/services/usb.h" #include "switch/services/usbds.h" +#include "switch/services/usbhs.h" #include "switch/services/hid.h" #include "switch/services/irs.h" #include "switch/services/pl.h" diff --git a/nx/include/switch/services/usbhs.h b/nx/include/switch/services/usbhs.h new file mode 100644 index 00000000..6612f6bb --- /dev/null +++ b/nx/include/switch/services/usbhs.h @@ -0,0 +1,128 @@ +/** + * @file usb.h + * @brief USB (usb:hs) devices service IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../services/sm.h" +#include "../services/usb.h" +#include "../kernel/event.h" + +typedef enum { + ///< These use \ref usb_device_descriptor. Bit2..6 require [6.0.0+], these are ignored on eariler versions. + UsbHsInterfaceFilterFlags_idVendor = BIT(0), + UsbHsInterfaceFilterFlags_idProduct = BIT(1), + UsbHsInterfaceFilterFlags_bcdDevice_Min = BIT(2), + UsbHsInterfaceFilterFlags_bcdDevice_Max = BIT(3), + UsbHsInterfaceFilterFlags_bDeviceClass = BIT(4), + UsbHsInterfaceFilterFlags_bDeviceSubClass = BIT(5), + UsbHsInterfaceFilterFlags_bDeviceProtocol = BIT(6), + + ///< These use \ref usb_interface_descriptor. + UsbHsInterfaceFilterFlags_bInterfaceClass = BIT(7), + UsbHsInterfaceFilterFlags_bInterfaceSubClass = BIT(8), + UsbHsInterfaceFilterFlags_bInterfaceProtocol = BIT(9), +} UsbHsInterfaceFilterFlags; + +/// Interface filtering struct. When the associated flag bit is set, the associated descriptor field and struct field are compared, on mismatch the interface is filtered out. +typedef struct { + u16 Flags; ///< See \ref UsbHsInterfaceFilterFlags. Setting this to 0 is equivalent to disabling filtering. + u16 idVendor; + u16 idProduct; + u16 bcdDevice_Min; ///< Descriptor value must be >= bcdDevice_Min. + u16 bcdDevice_Max; ///< Descriptor value must be <= bcdDevice_Max. + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; +} UsbHsInterfaceFilter; + +/// Interface struct. Note that devices have a seperate \ref UsbHsInterface for each interface. Descriptors which are not available are set to all-zero. +typedef struct { + s32 ID; + u32 deviceID_2; + u32 unk_x8; + + struct usb_interface_descriptor interface_desc; + u8 pad_x15[0x7]; + struct usb_endpoint_descriptor output_endpoint_descs[15]; + u8 pad_x85[0x7]; + struct usb_endpoint_descriptor input_endpoint_descs[15]; + u8 pad_xf5[0x6]; + struct usb_ss_endpoint_companion_descriptor output_ss_endpoint_companion_descs[15]; ///< ? + u8 pad_x155[0x6]; + struct usb_ss_endpoint_companion_descriptor input_ss_endpoint_companion_descs[15]; ///< ? + u8 pad_x1b5[0x3]; + + char pathstr[0x40]; + u32 busID; + u32 deviceID; + + struct usb_device_descriptor device_desc; + struct usb_config_descriptor config_desc; + u8 pad_x21b[0x5]; + + u64 timestamp; ///< Unknown u64 timestamp for when the device was inserted? +} PACKED UsbHsInterface; + +typedef struct { + u32 unk_x0; + Result res; + u32 unk_x8; + u32 transferredSize; + u64 unk_x10; +} UsbHsXferReport; + +/// Initialize/exit usb:hs. +Result usbHsInitialize(void); +void usbHsExit(void); + +/// Returns the Event loaded during init with autoclear=false. +Event* usbHsGetInterfaceStateChangeEvent(void); + +/** + * @brief Returns an array of all \ref UsbHsInterface. Internally this loads the same interfaces as \ref usbHsQueryAvailableInterfaces, followed by \ref usbHsQueryAcquiredInterfaces. + * @param[in] filter \ref UsbHsInterfaceFilter. + * @param[out] interfaces Array of output interfaces. + * @param[in] interfaces_maxsize Max byte-size of the interfaces buffer. + * @param[out] total_entries Total number of output interfaces. + */ +Result usbHsQueryAllInterfaces(const UsbHsInterfaceFilter* filter, UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries); + +/** + * @brief Returns an array of \ref UsbHsInterface which are available. + * @param[in] filter \ref UsbHsInterfaceFilter. + * @param[out] interfaces Array of output interfaces. + * @param[in] interfaces_maxsize Max byte-size of the interfaces buffer. + * @param[out] total_entries Total number of output interfaces. + */ +Result usbHsQueryAvailableInterfaces(const UsbHsInterfaceFilter* filter, UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries); + +/** + * @brief Returns an array of \ref UsbHsInterface which were previously acquired. + * @param[out] interfaces Array of output interfaces. + * @param[in] interfaces_maxsize Max byte-size of the interfaces buffer. + * @param[out] total_entries Total number of output interfaces. + */ +Result usbHsQueryAcquiredInterfaces(UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries); + +/** + * @brief Creates an event which is signaled when an interface is available which passes the filtering checks. + * @param[out] event Event object. + * @param[in] autoclear Event autoclear. + * @param[in] index Event index, must be 0..2. + * @param[in] filter \ref UsbHsInterfaceFilter. + */ +Result usbHsCreateInterfaceAvailableEvent(Event* event, bool autoclear, u8 index, const UsbHsInterfaceFilter* filter); + +/** + * @brief Destroys an event setup by \ref usbHsCreateInterfaceAvailableEvent. This *must* be used at some point during cleanup. + * @param[in] event Event object to close. + * @param[in] index Event index, must be 0..2. + */ +Result usbHsDestroyInterfaceAvailableEvent(Event* event, u8 index); + diff --git a/nx/source/services/usbhs.c b/nx/source/services/usbhs.c new file mode 100644 index 00000000..600a08f5 --- /dev/null +++ b/nx/source/services/usbhs.c @@ -0,0 +1,293 @@ +#include +#include "types.h" +#include "result.h" +#include "arm/cache.h" +#include "kernel/ipc.h" +#include "kernel/detect.h" +#include "services/usb.h" +#include "services/usbhs.h" +#include "services/sm.h" + +static Service g_usbHsSrv; +static Event g_usbHsInterfaceStateChangeEvent = {0}; + +static Result _usbHsBindClientProcess(Handle prochandle); +static Result _usbHsGetEvent(Service* srv, Event* event_out, u64 cmd_id); + +Result usbHsInitialize(void) { + if (serviceIsActive(&g_usbHsSrv)) + return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + Result rc = 0; + + rc = smGetService(&g_usbHsSrv, "usb:hs"); + + if (R_SUCCEEDED(rc)) { + rc = serviceConvertToDomain(&g_usbHsSrv); + } + + if (R_SUCCEEDED(rc) && kernelAbove200()) + rc = _usbHsBindClientProcess(CUR_PROCESS_HANDLE); + + // GetInterfaceStateChangeEvent + if (R_SUCCEEDED(rc)) + rc = _usbHsGetEvent(&g_usbHsSrv, &g_usbHsInterfaceStateChangeEvent, kernelAbove200() ? 6 : 5); + + if (R_FAILED(rc)) + { + eventClose(&g_usbHsInterfaceStateChangeEvent); + + serviceClose(&g_usbHsSrv); + } + + return rc; +} + +void usbHsExit(void) { + if (!serviceIsActive(&g_usbHsSrv)) + return; + + eventClose(&g_usbHsInterfaceStateChangeEvent); + + serviceClose(&g_usbHsSrv); +} + +Event* usbHsGetInterfaceStateChangeEvent(void) { + return &g_usbHsInterfaceStateChangeEvent; +} + +static Result _usbHsBindClientProcess(Handle prochandle) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + ipcSendHandleCopy(&c, prochandle); + + raw = serviceIpcPrepareHeader(&g_usbHsSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + + Result rc = serviceIpcDispatch(&g_usbHsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_usbHsSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _usbHsGetEvent(Service* srv, Event* event_out, u64 cmd_id) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + + Result rc = serviceIpcDispatch(srv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(srv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + eventLoadRemote(event_out, r.Handles[0], false); + } + } + + return rc; +} + +static Result _usbHsQueryInterfaces(u64 base_cmdid, const UsbHsInterfaceFilter* filter, UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + UsbHsInterfaceFilter filter; + } *raw; + + ipcAddRecvBuffer(&c, interfaces, interfaces_maxsize, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_usbHsSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = kernelAbove200() ? base_cmdid+1 : base_cmdid; + raw->filter = *filter; + + Result rc = serviceIpcDispatch(&g_usbHsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + s32 total_entries; + } *resp; + + serviceIpcParse(&g_usbHsSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +Result usbHsQueryAllInterfaces(const UsbHsInterfaceFilter* filter, UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries) { + return _usbHsQueryInterfaces(0, filter, interfaces, interfaces_maxsize, total_entries); +} + +Result usbHsQueryAvailableInterfaces(const UsbHsInterfaceFilter* filter, UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries) { + return _usbHsQueryInterfaces(1, filter, interfaces, interfaces_maxsize, total_entries); +} + +Result usbHsQueryAcquiredInterfaces(UsbHsInterface* interfaces, size_t interfaces_maxsize, s32* total_entries) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + ipcAddRecvBuffer(&c, interfaces, interfaces_maxsize, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_usbHsSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = kernelAbove200() ? 3 : 2; + + Result rc = serviceIpcDispatch(&g_usbHsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + s32 total_entries; + } *resp; + + serviceIpcParse(&g_usbHsSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +Result usbHsCreateInterfaceAvailableEvent(Event* event, bool autoclear, u8 index, const UsbHsInterfaceFilter* filter) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 index; + u8 pad; + UsbHsInterfaceFilter filter; + } *raw; + + raw = serviceIpcPrepareHeader(&g_usbHsSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = kernelAbove200() ? 4 : 3; + raw->index = index; + raw->filter = *filter; + + Result rc = serviceIpcDispatch(&g_usbHsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_usbHsSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + eventLoadRemote(event, r.Handles[0], autoclear); + } + } + + return rc; +} + +Result usbHsDestroyInterfaceAvailableEvent(Event* event, u8 index) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 index; + } *raw; + + raw = serviceIpcPrepareHeader(&g_usbHsSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = kernelAbove200() ? 5 : 4; + raw->index = index; + + Result rc = serviceIpcDispatch(&g_usbHsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_usbHsSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + eventClose(event); + + return rc; +} +