| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * (C) Copyright 2003 | |
| * Gerry Hamel, geh@ti.com, Texas Instruments | |
| * | |
| * (C) Copyright 2006 | |
| * Bryan O'Donoghue, deckard@CodeHermit.ie | |
| * | |
| * Based on | |
| * linux/drivers/usbd/ep0.c | |
| * | |
| * Copyright (c) 2000, 2001, 2002 Lineo | |
| * Copyright (c) 2001 Hewlett Packard | |
| * | |
| * By: | |
| * Stuart Lynne <sl@lineo.com>, | |
| * Tom Rushworth <tbr@lineo.com>, | |
| * Bruce Balden <balden@lineo.com> | |
| */ | |
| /* | |
| * This is the builtin ep0 control function. It implements all required functionality | |
| * for responding to control requests (SETUP packets). | |
| * | |
| * XXX | |
| * | |
| * Currently we do not pass any SETUP packets (or other) to the configured | |
| * function driver. This may need to change. | |
| * | |
| * XXX | |
| * | |
| * As alluded to above, a simple callback cdc_recv_setup has been implemented | |
| * in the usb_device data structure to facilicate passing | |
| * Common Device Class packets to a function driver. | |
| * | |
| * XXX | |
| */ | |
| #include <serial.h> | |
| #include <usbdevice.h> | |
| #if 0 | |
| #define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args) | |
| #else | |
| #define dbg_ep0(lvl,fmt,args...) | |
| #endif | |
| __maybe_unused static char *usbd_device_descriptors[] = { | |
| "UNKNOWN", /* 0 */ | |
| "DEVICE", /* 1 */ | |
| "CONFIG", /* 2 */ | |
| "STRING", /* 3 */ | |
| "INTERFACE", /* 4 */ | |
| "ENDPOINT", /* 5 */ | |
| "DEVICE QUALIFIER", /* 6 */ | |
| "OTHER SPEED", /* 7 */ | |
| "INTERFACE POWER", /* 8 */ | |
| }; | |
| #define USBD_DEVICE_DESCRIPTORS(x) (((unsigned int)x <= USB_DESCRIPTOR_TYPE_INTERFACE_POWER) ? \ | |
| usbd_device_descriptors[x] : "UNKNOWN") | |
| __maybe_unused static char *usbd_device_states[] = { | |
| "STATE_INIT", | |
| "STATE_CREATED", | |
| "STATE_ATTACHED", | |
| "STATE_POWERED", | |
| "STATE_DEFAULT", | |
| "STATE_ADDRESSED", | |
| "STATE_CONFIGURED", | |
| "STATE_UNKNOWN", | |
| }; | |
| #define USBD_DEVICE_STATE(x) (((unsigned int)x <= STATE_UNKNOWN) ? usbd_device_states[x] : "UNKNOWN") | |
| __maybe_unused static char *usbd_device_requests[] = { | |
| "GET STATUS", /* 0 */ | |
| "CLEAR FEATURE", /* 1 */ | |
| "RESERVED", /* 2 */ | |
| "SET FEATURE", /* 3 */ | |
| "RESERVED", /* 4 */ | |
| "SET ADDRESS", /* 5 */ | |
| "GET DESCRIPTOR", /* 6 */ | |
| "SET DESCRIPTOR", /* 7 */ | |
| "GET CONFIGURATION", /* 8 */ | |
| "SET CONFIGURATION", /* 9 */ | |
| "GET INTERFACE", /* 10 */ | |
| "SET INTERFACE", /* 11 */ | |
| "SYNC FRAME", /* 12 */ | |
| }; | |
| #define USBD_DEVICE_REQUESTS(x) (((unsigned int)x <= USB_REQ_SYNCH_FRAME) ? usbd_device_requests[x] : "UNKNOWN") | |
| /* EP0 Configuration Set ********************************************************************* */ | |
| /** | |
| * ep0_get_status - fill in URB data with appropriate status | |
| * @device: | |
| * @urb: | |
| * @index: | |
| * @requesttype: | |
| * | |
| */ | |
| static int ep0_get_status (struct usb_device_instance *device, | |
| struct urb *urb, int index, int requesttype) | |
| { | |
| char *cp; | |
| urb->actual_length = 2; | |
| cp = (char*)urb->buffer; | |
| cp[0] = cp[1] = 0; | |
| switch (requesttype) { | |
| case USB_REQ_RECIPIENT_DEVICE: | |
| cp[0] = USB_STATUS_SELFPOWERED; | |
| break; | |
| case USB_REQ_RECIPIENT_INTERFACE: | |
| break; | |
| case USB_REQ_RECIPIENT_ENDPOINT: | |
| cp[0] = usbd_endpoint_halted (device, index); | |
| break; | |
| case USB_REQ_RECIPIENT_OTHER: | |
| urb->actual_length = 0; | |
| default: | |
| break; | |
| } | |
| dbg_ep0 (2, "%02x %02x", cp[0], cp[1]); | |
| return 0; | |
| } | |
| /** | |
| * ep0_get_one | |
| * @device: | |
| * @urb: | |
| * @result: | |
| * | |
| * Set a single byte value in the urb send buffer. Return non-zero to signal | |
| * a request error. | |
| */ | |
| static int ep0_get_one (struct usb_device_instance *device, struct urb *urb, | |
| __u8 result) | |
| { | |
| urb->actual_length = 1; /* XXX 2? */ | |
| ((char *) urb->buffer)[0] = result; | |
| return 0; | |
| } | |
| /** | |
| * copy_config | |
| * @urb: pointer to urb | |
| * @data: pointer to configuration data | |
| * @length: length of data | |
| * | |
| * Copy configuration data to urb transfer buffer if there is room for it. | |
| */ | |
| void copy_config (struct urb *urb, void *data, int max_length, | |
| int max_buf) | |
| { | |
| int available; | |
| int length; | |
| /*dbg_ep0(3, "-> actual: %d buf: %d max_buf: %d max_length: %d data: %p", */ | |
| /* urb->actual_length, urb->buffer_length, max_buf, max_length, data); */ | |
| if (!data) { | |
| dbg_ep0 (1, "data is NULL"); | |
| return; | |
| } | |
| length = max_length; | |
| if (length > max_length) { | |
| dbg_ep0 (1, "length: %d >= max_length: %d", length, | |
| max_length); | |
| return; | |
| } | |
| /*dbg_ep0(1, " actual: %d buf: %d max_buf: %d max_length: %d length: %d", */ | |
| /* urb->actual_length, urb->buffer_length, max_buf, max_length, length); */ | |
| if ((available = | |
| /*urb->buffer_length */ max_buf - urb->actual_length) <= 0) { | |
| return; | |
| } | |
| /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ | |
| /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ | |
| if (length > available) { | |
| length = available; | |
| } | |
| /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ | |
| /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ | |
| memcpy (urb->buffer + urb->actual_length, data, length); | |
| urb->actual_length += length; | |
| dbg_ep0 (3, | |
| "copy_config: <- actual: %d buf: %d max_buf: %d max_length: %d available: %d", | |
| urb->actual_length, urb->buffer_length, max_buf, max_length, | |
| available); | |
| } | |
| /** | |
| * ep0_get_descriptor | |
| * @device: | |
| * @urb: | |
| * @max: | |
| * @descriptor_type: | |
| * @index: | |
| * | |
| * Called by ep0_rx_process for a get descriptor device command. Determine what | |
| * descriptor is being requested, copy to send buffer. Return zero if ok to send, | |
| * return non-zero to signal a request error. | |
| */ | |
| static int ep0_get_descriptor (struct usb_device_instance *device, | |
| struct urb *urb, int max, int descriptor_type, | |
| int index) | |
| { | |
| int port = 0; /* XXX compound device */ | |
| /*dbg_ep0(3, "max: %x type: %x index: %x", max, descriptor_type, index); */ | |
| if (!urb || !urb->buffer || !urb->buffer_length | |
| || (urb->buffer_length < 255)) { | |
| dbg_ep0 (2, "invalid urb %p", urb); | |
| return -1L; | |
| } | |
| /* setup tx urb */ | |
| urb->actual_length = 0; | |
| dbg_ep0 (2, "%s", USBD_DEVICE_DESCRIPTORS (descriptor_type)); | |
| switch (descriptor_type) { | |
| case USB_DESCRIPTOR_TYPE_DEVICE: | |
| { | |
| struct usb_device_descriptor *device_descriptor; | |
| if (! | |
| (device_descriptor = | |
| usbd_device_device_descriptor (device, port))) { | |
| return -1; | |
| } | |
| /* copy descriptor for this device */ | |
| copy_config (urb, device_descriptor, | |
| sizeof (struct usb_device_descriptor), | |
| max); | |
| /* correct the correct control endpoint 0 max packet size into the descriptor */ | |
| device_descriptor = | |
| (struct usb_device_descriptor *) urb->buffer; | |
| } | |
| dbg_ep0(3, "copied device configuration, actual_length: 0x%x", urb->actual_length); | |
| break; | |
| case USB_DESCRIPTOR_TYPE_CONFIGURATION: | |
| { | |
| struct usb_configuration_descriptor | |
| *configuration_descriptor; | |
| struct usb_device_descriptor *device_descriptor; | |
| if (! | |
| (device_descriptor = | |
| usbd_device_device_descriptor (device, port))) { | |
| return -1; | |
| } | |
| /*dbg_ep0(2, "%d %d", index, device_descriptor->bNumConfigurations); */ | |
| if (index >= device_descriptor->bNumConfigurations) { | |
| dbg_ep0 (0, "index too large: %d >= %d", index, | |
| device_descriptor-> | |
| bNumConfigurations); | |
| return -1; | |
| } | |
| if (! | |
| (configuration_descriptor = | |
| usbd_device_configuration_descriptor (device, | |
| port, | |
| index))) { | |
| dbg_ep0 (0, | |
| "usbd_device_configuration_descriptor failed: %d", | |
| index); | |
| return -1; | |
| } | |
| dbg_ep0(0, "attempt to copy %d bytes to urb\n",cpu_to_le16(configuration_descriptor->wTotalLength)); | |
| copy_config (urb, configuration_descriptor, | |
| cpu_to_le16(configuration_descriptor->wTotalLength), | |
| max); | |
| } | |
| break; | |
| case USB_DESCRIPTOR_TYPE_STRING: | |
| { | |
| struct usb_string_descriptor *string_descriptor; | |
| if (!(string_descriptor = usbd_get_string (index))) { | |
| dbg_ep0(0, "Invalid string index %d\n", index); | |
| return -1; | |
| } | |
| dbg_ep0(3, "string_descriptor: %p length %d", string_descriptor, string_descriptor->bLength); | |
| copy_config (urb, string_descriptor, string_descriptor->bLength, max); | |
| } | |
| break; | |
| case USB_DESCRIPTOR_TYPE_INTERFACE: | |
| dbg_ep0(2, "USB_DESCRIPTOR_TYPE_INTERFACE - error not implemented\n"); | |
| return -1; | |
| case USB_DESCRIPTOR_TYPE_ENDPOINT: | |
| dbg_ep0(2, "USB_DESCRIPTOR_TYPE_ENDPOINT - error not implemented\n"); | |
| return -1; | |
| case USB_DESCRIPTOR_TYPE_HID: | |
| { | |
| dbg_ep0(2, "USB_DESCRIPTOR_TYPE_HID - error not implemented\n"); | |
| return -1; /* unsupported at this time */ | |
| #if 0 | |
| int bNumInterface = | |
| le16_to_cpu (urb->device_request.wIndex); | |
| int bAlternateSetting = 0; | |
| int class = 0; | |
| struct usb_class_descriptor *class_descriptor; | |
| if (!(class_descriptor = | |
| usbd_device_class_descriptor_index (device, | |
| port, 0, | |
| bNumInterface, | |
| bAlternateSetting, | |
| class)) | |
| || class_descriptor->descriptor.hid.bDescriptorType != USB_DT_HID) { | |
| dbg_ep0 (3, "[%d] interface is not HID", | |
| bNumInterface); | |
| return -1; | |
| } | |
| /* copy descriptor for this class */ | |
| copy_config (urb, class_descriptor, | |
| class_descriptor->descriptor.hid.bLength, | |
| max); | |
| #endif | |
| } | |
| break; | |
| case USB_DESCRIPTOR_TYPE_REPORT: | |
| { | |
| dbg_ep0(2, "USB_DESCRIPTOR_TYPE_REPORT - error not implemented\n"); | |
| return -1; /* unsupported at this time */ | |
| #if 0 | |
| int bNumInterface = | |
| le16_to_cpu (urb->device_request.wIndex); | |
| int bAlternateSetting = 0; | |
| int class = 0; | |
| struct usb_class_report_descriptor *report_descriptor; | |
| if (!(report_descriptor = | |
| usbd_device_class_report_descriptor_index | |
| (device, port, 0, bNumInterface, | |
| bAlternateSetting, class)) | |
| || report_descriptor->bDescriptorType != | |
| USB_DT_REPORT) { | |
| dbg_ep0 (3, "[%d] descriptor is not REPORT", | |
| bNumInterface); | |
| return -1; | |
| } | |
| /* copy report descriptor for this class */ | |
| /*copy_config(urb, &report_descriptor->bData[0], report_descriptor->wLength, max); */ | |
| if (max - urb->actual_length > 0) { | |
| int length = | |
| min(report_descriptor->wLength, | |
| max - urb->actual_length); | |
| memcpy (urb->buffer + urb->actual_length, | |
| &report_descriptor->bData[0], length); | |
| urb->actual_length += length; | |
| } | |
| #endif | |
| } | |
| break; | |
| case USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: | |
| return -1; | |
| default: | |
| return -1; | |
| } | |
| dbg_ep0 (1, "urb: buffer: %p buffer_length: %2d actual_length: %2d tx_packetSize: %2d", | |
| urb->buffer, urb->buffer_length, urb->actual_length, | |
| device->bus->endpoint_array[0].tx_packetSize); | |
| /* | |
| if ((urb->actual_length < max) && !(urb->actual_length % device->bus->endpoint_array[0].tx_packetSize)) { | |
| dbg_ep0(0, "adding null byte"); | |
| urb->buffer[urb->actual_length++] = 0; | |
| dbg_ep0(0, "urb: buffer_length: %2d actual_length: %2d packet size: %2d", | |
| urb->buffer_length, urb->actual_length device->bus->endpoint_array[0].tx_packetSize); | |
| } | |
| */ | |
| return 0; | |
| } | |
| /** | |
| * ep0_recv_setup - called to indicate URB has been received | |
| * @urb: pointer to struct urb | |
| * | |
| * Check if this is a setup packet, process the device request, put results | |
| * back into the urb and return zero or non-zero to indicate success (DATA) | |
| * or failure (STALL). | |
| * | |
| */ | |
| int ep0_recv_setup (struct urb *urb) | |
| { | |
| /*struct usb_device_request *request = urb->buffer; */ | |
| /*struct usb_device_instance *device = urb->device; */ | |
| struct usb_device_request *request; | |
| struct usb_device_instance *device; | |
| int address; | |
| dbg_ep0 (0, "entering ep0_recv_setup()"); | |
| if (!urb || !urb->device) { | |
| dbg_ep0 (3, "invalid URB %p", urb); | |
| return -1; | |
| } | |
| request = &urb->device_request; | |
| device = urb->device; | |
| dbg_ep0 (3, "urb: %p device: %p", urb, urb->device); | |
| /*dbg_ep0(2, "- - - - - - - - - -"); */ | |
| dbg_ep0 (2, | |
| "bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x %s", | |
| request->bmRequestType, request->bRequest, | |
| le16_to_cpu (request->wValue), le16_to_cpu (request->wIndex), | |
| le16_to_cpu (request->wLength), | |
| USBD_DEVICE_REQUESTS (request->bRequest)); | |
| /* handle USB Standard Request (c.f. USB Spec table 9-2) */ | |
| if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) { | |
| if(device->device_state <= STATE_CONFIGURED){ | |
| /* Attempt to handle a CDC specific request if we are | |
| * in the configured state. | |
| */ | |
| return device->cdc_recv_setup(request,urb); | |
| } | |
| dbg_ep0 (1, "non standard request: %x", | |
| request->bmRequestType & USB_REQ_TYPE_MASK); | |
| return -1; /* Stall here */ | |
| } | |
| switch (device->device_state) { | |
| case STATE_CREATED: | |
| case STATE_ATTACHED: | |
| case STATE_POWERED: | |
| /* It actually is important to allow requests in these states, | |
| * Windows will request descriptors before assigning an | |
| * address to the client. | |
| */ | |
| /*dbg_ep0 (1, "request %s not allowed in this state: %s", */ | |
| /* USBD_DEVICE_REQUESTS(request->bRequest), */ | |
| /* usbd_device_states[device->device_state]); */ | |
| /*return -1; */ | |
| break; | |
| case STATE_INIT: | |
| case STATE_DEFAULT: | |
| switch (request->bRequest) { | |
| case USB_REQ_GET_STATUS: | |
| case USB_REQ_GET_INTERFACE: | |
| case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ | |
| case USB_REQ_CLEAR_FEATURE: | |
| case USB_REQ_SET_FEATURE: | |
| case USB_REQ_SET_DESCRIPTOR: | |
| /* case USB_REQ_SET_CONFIGURATION: */ | |
| case USB_REQ_SET_INTERFACE: | |
| dbg_ep0 (1, | |
| "request %s not allowed in DEFAULT state: %s", | |
| USBD_DEVICE_REQUESTS (request->bRequest), | |
| usbd_device_states[device->device_state]); | |
| return -1; | |
| case USB_REQ_SET_CONFIGURATION: | |
| case USB_REQ_SET_ADDRESS: | |
| case USB_REQ_GET_DESCRIPTOR: | |
| case USB_REQ_GET_CONFIGURATION: | |
| break; | |
| } | |
| case STATE_ADDRESSED: | |
| case STATE_CONFIGURED: | |
| break; | |
| case STATE_UNKNOWN: | |
| dbg_ep0 (1, "request %s not allowed in UNKNOWN state: %s", | |
| USBD_DEVICE_REQUESTS (request->bRequest), | |
| usbd_device_states[device->device_state]); | |
| return -1; | |
| } | |
| /* handle all requests that return data (direction bit set on bm RequestType) */ | |
| if ((request->bmRequestType & USB_REQ_DIRECTION_MASK)) { | |
| dbg_ep0 (3, "Device-to-Host"); | |
| switch (request->bRequest) { | |
| case USB_REQ_GET_STATUS: | |
| return ep0_get_status (device, urb, request->wIndex, | |
| request->bmRequestType & | |
| USB_REQ_RECIPIENT_MASK); | |
| case USB_REQ_GET_DESCRIPTOR: | |
| return ep0_get_descriptor (device, urb, | |
| le16_to_cpu (request->wLength), | |
| le16_to_cpu (request->wValue) >> 8, | |
| le16_to_cpu (request->wValue) & 0xff); | |
| case USB_REQ_GET_CONFIGURATION: | |
| dbg_ep0(2, "get config %d\n", device->configuration); | |
| return ep0_get_one (device, urb, | |
| device->configuration); | |
| case USB_REQ_GET_INTERFACE: | |
| return ep0_get_one (device, urb, device->alternate); | |
| case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ | |
| return -1; | |
| case USB_REQ_CLEAR_FEATURE: | |
| case USB_REQ_SET_FEATURE: | |
| case USB_REQ_SET_ADDRESS: | |
| case USB_REQ_SET_DESCRIPTOR: | |
| case USB_REQ_SET_CONFIGURATION: | |
| case USB_REQ_SET_INTERFACE: | |
| return -1; | |
| } | |
| } | |
| /* handle the requests that do not return data */ | |
| else { | |
| /*dbg_ep0(3, "Host-to-Device"); */ | |
| switch (request->bRequest) { | |
| case USB_REQ_CLEAR_FEATURE: | |
| case USB_REQ_SET_FEATURE: | |
| dbg_ep0 (0, "Host-to-Device"); | |
| switch (request-> | |
| bmRequestType & USB_REQ_RECIPIENT_MASK) { | |
| case USB_REQ_RECIPIENT_DEVICE: | |
| /* XXX DEVICE_REMOTE_WAKEUP or TEST_MODE would be added here */ | |
| /* XXX fall through for now as we do not support either */ | |
| case USB_REQ_RECIPIENT_INTERFACE: | |
| case USB_REQ_RECIPIENT_OTHER: | |
| dbg_ep0 (0, "request %s not", | |
| USBD_DEVICE_REQUESTS (request->bRequest)); | |
| default: | |
| return -1; | |
| case USB_REQ_RECIPIENT_ENDPOINT: | |
| dbg_ep0 (0, "ENDPOINT: %x", le16_to_cpu (request->wValue)); | |
| if (le16_to_cpu (request->wValue) == USB_ENDPOINT_HALT) { | |
| /*return usbd_device_feature (device, le16_to_cpu (request->wIndex), */ | |
| /* request->bRequest == USB_REQ_SET_FEATURE); */ | |
| /* NEED TO IMPLEMENT THIS!!! */ | |
| return -1; | |
| } else { | |
| dbg_ep0 (1, "request %s bad wValue: %04x", | |
| USBD_DEVICE_REQUESTS | |
| (request->bRequest), | |
| le16_to_cpu (request->wValue)); | |
| return -1; | |
| } | |
| } | |
| case USB_REQ_SET_ADDRESS: | |
| /* check if this is a re-address, reset first if it is (this shouldn't be possible) */ | |
| if (device->device_state != STATE_DEFAULT) { | |
| dbg_ep0 (1, "set_address: %02x state: %s", | |
| le16_to_cpu (request->wValue), | |
| usbd_device_states[device->device_state]); | |
| return -1; | |
| } | |
| address = le16_to_cpu (request->wValue); | |
| if ((address & 0x7f) != address) { | |
| dbg_ep0 (1, "invalid address %04x %04x", | |
| address, address & 0x7f); | |
| return -1; | |
| } | |
| device->address = address; | |
| /*dbg_ep0(2, "address: %d %d %d", */ | |
| /* request->wValue, le16_to_cpu(request->wValue), device->address); */ | |
| return 0; | |
| case USB_REQ_SET_DESCRIPTOR: /* XXX should we support this? */ | |
| dbg_ep0 (0, "set descriptor: NOT SUPPORTED"); | |
| return -1; | |
| case USB_REQ_SET_CONFIGURATION: | |
| /* c.f. 9.4.7 - the top half of wValue is reserved */ | |
| device->configuration = le16_to_cpu(request->wValue) & 0xff; | |
| /* reset interface and alternate settings */ | |
| device->interface = device->alternate = 0; | |
| /*dbg_ep0(2, "set configuration: %d", device->configuration); */ | |
| /*dbg_ep0(2, "DEVICE_CONFIGURED.. event?\n"); */ | |
| return 0; | |
| case USB_REQ_SET_INTERFACE: | |
| device->interface = le16_to_cpu (request->wIndex); | |
| device->alternate = le16_to_cpu (request->wValue); | |
| /*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */ | |
| dbg_ep0(2, "DEVICE_SET_INTERFACE.. event?\n"); | |
| return 0; | |
| case USB_REQ_GET_STATUS: | |
| case USB_REQ_GET_DESCRIPTOR: | |
| case USB_REQ_GET_CONFIGURATION: | |
| case USB_REQ_GET_INTERFACE: | |
| case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ | |
| return -1; | |
| } | |
| } | |
| return -1; | |
| } |