/*- * Copyright 2022 John-Mark Gurney. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. If you are STMicroelectronics N.V., one of it's subsidiaries, a * subsidiary of an owner of STMicroelectronics N.V., or an employee, * contractor, or agent of any of the preceeding entities, you are not * allowed to use this code, in either source or binary forms. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "usb_hid_def.h" #include /* debug_printf */ #include #include static uint8_t Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx); static uint8_t DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx); static uint8_t Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); static uint8_t DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum); #if 0 static uint8_t DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum); #endif static uint8_t ep0rxready(USBD_HandleTypeDef *pdev); #define USB_HID_EPIN_ADDR 0x81 #define REPORT_CNT 10 enum hid_state { IDLE, BUSY, }; struct usb_hid_softc { enum hid_state state; uint8_t led_status[2]; uint8_t idle_time; uint8_t report_protocol; }; static USBD_HandleTypeDef *hid_handle; static uint32_t next_idle_report; static uint8_t reports[REPORT_CNT][REPORT_SIZE] = { /* mod pad keys... */ #if 0 [0] = { 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00 }, [1] = { 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, [2] = { 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }, [3] = { 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00 }, [4] = { 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00 }, [5] = { 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 }, [6] = { 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00 }, [7] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, [8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, [9] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, #endif }; /* * Ring buffer for reports. * * reporthead: next available report * reporttail: next empty slot * * reporthead == reporttail: ring buffer is empty * reporttail == reporthead - 1: ring buffer is full */ static uint8_t reporthead = 0; static uint8_t reporttail = 0; static uint32_t reportoverflow; /* as -1 % posint == -1, adding REPORT_CNT keeps it positive if x is val - 1 when val is 0 */ #define MAKE_POS(x) (((x) + REPORT_CNT) % REPORT_CNT) static bool report_is_avail(void) { return reporthead != reporttail; } static uint8_t * report_next(void) { int oldrep; /* no new report, don't advance, return previous */ if (!report_is_avail()) return reports[MAKE_POS(reporthead - 1)]; oldrep = reporthead; reporthead = MAKE_POS(reporthead + 1); return reports[oldrep]; } void report_process(void) { struct usb_hid_softc *hid; uint32_t tick; if (hid_handle == NULL) return; hid = (struct usb_hid_softc *)hid_handle->pClassData; if (hid_handle->dev_state != USBD_STATE_CONFIGURED || hid->state != IDLE) { return; } tick = HAL_GetTick(); if (!report_is_avail() && tick < next_idle_report) return; next_idle_report = tick + hid->idle_time; hid->state = BUSY; USBD_LL_Transmit(hid_handle, USB_HID_EPIN_ADDR, report_next(), REPORT_SIZE); } /* * This could be smarter by collapsing reports. The algorithm to do * so is a bit tricky, and likely not needed, BUT it does mean for a * simple algorithm, you could be limited to 50 chars per second assuming * 10ms report timing (one down, one up). * * Return true if successful, false if overflowed. */ int report_insert(uint8_t rep[REPORT_SIZE]) { uint8_t newtail; newtail = MAKE_POS(reporttail + 1); if (newtail == reporthead) { reportoverflow++; return 0; } memcpy(reports[reporttail], rep, sizeof reports[reporttail]); reporttail = newtail; return 1; } enum { HID_REQ_GET_REPORT = 0x01, HID_REQ_GET_IDLE = 0x02, HID_REQ_GET_PROTOCOL = 0x03, HID_REQ_SET_REPORT = 0x09, HID_REQ_SET_IDLE = 0x0a, HID_REQ_SET_PROTOCOL = 0x0b, }; USBD_ClassTypeDef usb_hid_def = { .Init = Init, .DeInit = DeInit, .Setup = Setup, .EP0_RxReady = ep0rxready, .DataIn = DataIn, #if 0 .DataOut = DataOut, #endif /* Get*Desc not used */ }; static uint8_t Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { struct usb_hid_softc *hid; int ret; ret = 0; usb_hid_epopen(pdev); /* allocate state data */ hid = (void *)USBD_malloc(sizeof(struct usb_hid_softc)); if (hid == NULL) { ret = 1; } else { *hid = (struct usb_hid_softc){ .idle_time = 10, }; pdev->pClassData = hid; hid_handle = pdev; } return ret; } static uint8_t DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { usb_hid_epclose(pdev); if (pdev->pClassData != NULL) { USBD_free(pdev->pClassData); pdev->pClassData = NULL; hid_handle = NULL; } return 0; } #define MAKE_CASE_REQ(type, req) ((((type) << 8) & USB_REQ_TYPE_MASK) | (req)) #define USB_CASE_REQ(req) MAKE_CASE_REQ((req)->bmRequest, (req)->bRequest) static void send_ctlresp(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req, const uint8_t *buf, size_t len) { len = MIN(req->wLength, len); USBD_CtlSendData(pdev, (uint8_t *)(uintptr_t)buf, len); } /* * Handle non-standard control SETUP requests. * * This includes requests sent to the interface and other special * requests. */ static uint8_t Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { struct usb_hid_softc *hid; uint8_t ret; ret = USBD_OK; hid = (struct usb_hid_softc *)pdev->pClassData; switch (USB_CASE_REQ(req)) { case MAKE_CASE_REQ(USB_REQ_TYPE_STANDARD, USB_REQ_GET_DESCRIPTOR): USBD_GetDescriptor(pdev, req); break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_GET_REPORT): send_ctlresp(pdev, req, report_next(), REPORT_SIZE); break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_SET_REPORT): break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_GET_IDLE): send_ctlresp(pdev, req, &hid->idle_time, sizeof hid->idle_time); break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_SET_IDLE): hid->idle_time = (uint8_t)(req->wValue >> 8); break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_GET_PROTOCOL): send_ctlresp(pdev, req, &hid->report_protocol, sizeof hid->idle_time); break; case MAKE_CASE_REQ(USB_REQ_TYPE_CLASS, HID_REQ_SET_PROTOCOL): hid->report_protocol = !!req->wValue; break; default: USBD_CtlError(pdev, req); ret = USBD_FAIL; break; } return ret; } static uint8_t DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) { struct usb_hid_softc *hid; hid = (struct usb_hid_softc *)pdev->pClassData; /* Previously queued data was sent */ hid->state = IDLE; return USBD_OK; } #if 0 static uint8_t DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum) { /* received led status */ return USBD_OK; } #endif static uint8_t ep0rxready(USBD_HandleTypeDef *pdev) { return USBD_OK; }