diff --git a/class/cdc/usbh_cdc_acm.c b/class/cdc/usbh_cdc_acm.c new file mode 100644 index 00000000..ba4790fa --- /dev/null +++ b/class/cdc/usbh_cdc_acm.c @@ -0,0 +1,328 @@ +/** + * @file usbh_cdc_acm.c + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#include "usbh_core.h" +#include "usbh_cdc_acm.h" + +#define DEV_FORMAT "/dev/ttyACM%d" +#define DEV_NAMELEN 16 + +static uint32_t g_devinuse = 0; + +void usbh_cdc_acm_callback(void *arg, ssize_t nbytes); + +/**************************************************************************** + * Name: usbh_cdc_acm_devno_alloc + * + * Description: + * Allocate a unique /dev/ttyACM[n] minor number in the range 0-31. + * + ****************************************************************************/ + +static int usbh_cdc_acm_devno_alloc(struct usbh_cdc_acm *priv) +{ + uint32_t flags; + int devno; + + flags = usb_osal_enter_critical_section(); + for (devno = 0; devno < 32; devno++) { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) { + g_devinuse |= bitno; + priv->minor = devno; + usb_osal_leave_critical_section(flags); + return 0; + } + } + + usb_osal_leave_critical_section(flags); + return -EMFILE; +} + +/**************************************************************************** + * Name: usbh_cdc_acm_devno_free + * + * Description: + * Free a /dev/ttyACM[n] minor number so that it can be used. + * + ****************************************************************************/ + +static void usbh_cdc_acm_devno_free(struct usbh_cdc_acm *priv) +{ + int devno = priv->minor; + + if (devno >= 0 && devno < 32) { + uint32_t flags = usb_osal_enter_critical_section(); + g_devinuse &= ~(1 << devno); + usb_osal_leave_critical_section(flags); + } +} + +/**************************************************************************** + * Name: usbh_cdc_acm_mkdevname + * + * Description: + * Format a /dev/ttyACM[n] device name given a minor number. + * + ****************************************************************************/ + +static inline void usbh_cdc_acm_mkdevname(struct usbh_cdc_acm *priv, char *devname) +{ + snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor); +} + +int usbh_cdc_acm_set_line_coding(struct usbh_hubport *hport, uint8_t intf, struct cdc_line_coding *line_coding) +{ + int ret; + struct usb_setup_packet *setup; + struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv; + + setup = cdc_acm_class->setup; + + if (cdc_acm_class->ctrl_intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = CDC_REQUEST_SET_LINE_CODING; + setup->wValue = 0; + setup->wIndex = intf; + setup->wLength = 7; + + ret = usbh_control_transfer(hport->ep0, setup, (uint8_t *)line_coding); + if (ret < 0) { + return ret; + } + memcpy(cdc_acm_class->linecoding, line_coding, sizeof(struct cdc_line_coding)); + return 0; +} + +int usbh_cdc_acm_get_line_coding(struct usbh_hubport *hport, uint8_t intf, struct cdc_line_coding *line_coding) +{ + int ret; + struct usb_setup_packet *setup; + struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv; + + setup = cdc_acm_class->setup; + + if (cdc_acm_class->ctrl_intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = CDC_REQUEST_GET_LINE_CODING; + setup->wValue = 0; + setup->wIndex = intf; + setup->wLength = 7; + + ret = usbh_control_transfer(hport->ep0, setup, (uint8_t *)line_coding); + if (ret < 0) { + return ret; + } + memcpy(cdc_acm_class->linecoding, line_coding, sizeof(struct cdc_line_coding)); + return 0; +} + +int usbh_cdc_acm_set_line_state(struct usbh_hubport *hport, uint8_t intf, bool dtr, bool rts) +{ + int ret; + struct usb_setup_packet *setup; + struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv; + + setup = cdc_acm_class->setup; + + if (cdc_acm_class->ctrl_intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE; + setup->wValue = (dtr << 0) | (rts << 1); + setup->wIndex = intf; + setup->wLength = 0; + + ret = usbh_control_transfer(hport->ep0, setup, NULL); + if (ret < 0) { + return ret; + } + + cdc_acm_class->dtr = dtr; + cdc_acm_class->rts = rts; + + return 0; +} +USB_NOCACHE_RAM_SECTION uint8_t cdc_buffer[4096]; + +int usbh_cdc_acm_connect(struct usbh_hubport *hport, uint8_t intf) +{ + struct usbh_endpoint_cfg ep_cfg = { 0 }; + struct usb_endpoint_descriptor *ep_desc; + char devname[DEV_NAMELEN]; + int ret; + + struct usbh_cdc_acm *cdc_acm_class = usb_malloc(sizeof(struct usbh_cdc_acm)); + if (cdc_acm_class == NULL) { + return -ENOMEM; + } + + memset(cdc_acm_class, 0, sizeof(struct usbh_cdc_acm)); + + usbh_cdc_acm_devno_alloc(cdc_acm_class); + usbh_cdc_acm_mkdevname(cdc_acm_class, devname); + + hport->config.intf[intf].priv = cdc_acm_class; + hport->config.intf[intf + 1].priv = cdc_acm_class; + + cdc_acm_class->setup = usb_iomalloc(sizeof(struct usb_setup_packet)); + cdc_acm_class->linecoding = usb_iomalloc(sizeof(struct cdc_line_coding)); + + cdc_acm_class->ctrl_intf = intf; + cdc_acm_class->data_intf = intf + 1; + + cdc_acm_class->linecoding->dwDTERate = 115200; + cdc_acm_class->linecoding->bDataBits = 8; + cdc_acm_class->linecoding->bParityType = 0; + cdc_acm_class->linecoding->bCharFormat = 0; + ret = usbh_cdc_acm_set_line_coding(hport, intf, cdc_acm_class->linecoding); + if (ret < 0) { + return ret; + } + + ret = usbh_cdc_acm_set_line_state(hport, intf, true, true); + if (ret < 0) { + return ret; + } + +#if 0 + ep_desc = &hport->config.intf[intf].ep[0].ep_desc; + ep_cfg.ep_addr = ep_desc->bEndpointAddress; + ep_cfg.ep_type = ep_desc->bmAttributes & USB_ENDPOINT_TYPE_MASK; + ep_cfg.ep_mps = ep_desc->wMaxPacketSize; + ep_cfg.ep_interval = ep_desc->bInterval; + ep_cfg.hport = hport; + usbh_ep_alloc(&cdc_acm_class->intin, &ep_cfg); + +#endif + for (uint8_t i = 0; i < hport->config.intf[intf + 1].intf_desc.bNumEndpoints; i++) { + ep_desc = &hport->config.intf[intf + 1].ep[i].ep_desc; + + ep_cfg.ep_addr = ep_desc->bEndpointAddress; + ep_cfg.ep_type = ep_desc->bmAttributes & USB_ENDPOINT_TYPE_MASK; + ep_cfg.ep_mps = ep_desc->wMaxPacketSize; + ep_cfg.ep_interval = ep_desc->bInterval; + ep_cfg.hport = hport; + if (ep_desc->bEndpointAddress & 0x80) { + usbh_ep_alloc(&cdc_acm_class->bulkin, &ep_cfg); + } else { + usbh_ep_alloc(&cdc_acm_class->bulkout, &ep_cfg); + } + } + + USB_LOG_INFO("Register CDC ACM Class:%s\r\n", devname); + + memset(cdc_buffer, 0, 512); + ret = usbh_ep_bulk_transfer(cdc_acm_class->bulkin, cdc_buffer, 512); + if (ret < 0) { + printf("bulk in error\r\n"); + return ret; + } + printf("recv over:%d\r\n", ret); + for (size_t i = 0; i < ret; i++) { + printf("0x%02x ", cdc_buffer[i]); + } + printf("\r\n"); + const uint8_t data1[10] = { 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x08, 0x14 }; + + memcpy(cdc_buffer, data1, 8); + ret = usbh_ep_bulk_transfer(cdc_acm_class->bulkout, cdc_buffer, 8); + if (ret < 0) { + printf("bulk out error\r\n"); + return ret; + } + printf("send over:%d\r\n", ret); + +#if 0 + usbh_ep_bulk_async_transfer(cdc_acm_class->bulkin, cdc_buffer, 512, usbh_cdc_acm_callback, NULL); +#else + ret = usbh_ep_bulk_transfer(cdc_acm_class->bulkin, cdc_buffer, 512); + if (ret < 0) { + printf("bulk in error\r\n"); + return ret; + } + printf("recv over:%d\r\n", ret); + for (size_t i = 0; i < ret; i++) { + printf("0x%02x ", cdc_buffer[i]); + } + printf("\r\n"); +#endif + return ret; +} + +int usbh_cdc_acm_disconnect(struct usbh_hubport *hport, uint8_t intf) +{ + char devname[DEV_NAMELEN]; + int ret = 0; + + struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv; + + if (cdc_acm_class) { + usbh_cdc_acm_devno_free(cdc_acm_class); + usbh_cdc_acm_mkdevname(cdc_acm_class, devname); + + if (cdc_acm_class->bulkin) { + ret = usb_ep_cancel(cdc_acm_class->bulkin); + if (ret < 0) { + } + usbh_ep_free(cdc_acm_class->bulkin); + } + + if (cdc_acm_class->bulkout) { + ret = usb_ep_cancel(cdc_acm_class->bulkout); + if (ret < 0) { + } + usbh_ep_free(cdc_acm_class->bulkout); + } + if (cdc_acm_class->setup) + usb_iofree(cdc_acm_class->setup); + if (cdc_acm_class->linecoding) + usb_iofree(cdc_acm_class->linecoding); + + usb_free(cdc_acm_class); + + hport->config.intf[intf].priv = NULL; + hport->config.intf[intf + 1].priv = NULL; + + USB_LOG_INFO("Unregister CDC ACM Class:%s\r\n", devname); + } + + return ret; +} + +void usb_cdc_acm_callback(void *arg, ssize_t result) +{ + printf("result:%d\r\n", result); +} + +const struct usbh_class_driver cdc_acm_class_driver = { + .driver_name = "cdc_acm", + .connect = usbh_cdc_acm_connect, + .disconnect = usbh_cdc_acm_disconnect +}; \ No newline at end of file diff --git a/class/cdc/usbh_cdc_acm.h b/class/cdc/usbh_cdc_acm.h new file mode 100644 index 00000000..0de425e8 --- /dev/null +++ b/class/cdc/usbh_cdc_acm.h @@ -0,0 +1,52 @@ +/** + * @file usbh_cdc_acm.h + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _USBH_CDC_ACM_H +#define _USBH_CDC_ACM_H + +#include "usb_cdc.h" + +struct usbh_cdc_acm { + struct usb_setup_packet *setup; + struct cdc_line_coding *linecoding; + uint8_t ctrl_intf; /* Control interface number */ + uint8_t data_intf; /* Data interface number */ + bool dtr; + bool rts; + uint8_t minor; + usbh_epinfo_t bulkin; /* Bulk IN endpoint */ + usbh_epinfo_t bulkout; /* Bulk OUT endpoint */ +#ifdef HAVE_INTIN_ENDPOINT + usbh_epinfo_t intin; /* Interrupt IN endpoint (optional) */ +#endif +}; + +extern const struct usbh_class_driver cdc_acm_class_driver; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/class/hid/usbh_hid.c b/class/hid/usbh_hid.c new file mode 100644 index 00000000..c149216f --- /dev/null +++ b/class/hid/usbh_hid.c @@ -0,0 +1,270 @@ +/** + * @file usbh_hid.c + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#include "usbh_core.h" +#include "usbh_hid.h" + +#define DEV_FORMAT "/dev/input%d" +#define DEV_NAMELEN 16 + +static uint32_t g_devinuse = 0; + +/**************************************************************************** + * Name: usbh_hid_devno_alloc + * + * Description: + * Allocate a unique /dev/hid[n] minor number in the range 0-31. + * + ****************************************************************************/ + +static int usbh_hid_devno_alloc(struct usbh_hid *priv) +{ + uint32_t flags; + int devno; + + flags = usb_osal_enter_critical_section(); + for (devno = 0; devno < 32; devno++) { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) { + g_devinuse |= bitno; + priv->minor = devno; + usb_osal_leave_critical_section(flags); + return 0; + } + } + + usb_osal_leave_critical_section(flags); + return -EMFILE; +} + +/**************************************************************************** + * Name: usbh_hid_devno_free + * + * Description: + * Free a /dev/hid[n] minor number so that it can be used. + * + ****************************************************************************/ + +static void usbh_hid_devno_free(struct usbh_hid *priv) +{ + int devno = priv->minor; + + if (devno >= 0 && devno < 32) { + uint32_t flags = usb_osal_enter_critical_section(); + g_devinuse &= ~(1 << devno); + usb_osal_leave_critical_section(flags); + } +} + +/**************************************************************************** + * Name: usbh_hid_mkdevname + * + * Description: + * Format a /dev/hid[n] device name given a minor number. + * + ****************************************************************************/ + +static inline void usbh_hid_mkdevname(struct usbh_hid *priv, char *devname) +{ + snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor); +} + +int usbh_hid_get_report_descriptor(struct usbh_hubport *hport, uint8_t intf, uint8_t *buffer) +{ + struct usb_setup_packet *setup; + struct usbh_hid *hid_class = (struct usbh_hid *)hport->config.intf[intf].priv; + + setup = hid_class->setup; + + if (hid_class->intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = HID_DESCRIPTOR_TYPE_HID_REPORT << 8; + setup->wIndex = intf; + setup->wLength = 128; + + return usbh_control_transfer(hport->ep0, setup, buffer); +} + +int usbh_hid_set_idle(struct usbh_hubport *hport, uint8_t intf, uint8_t report_id, uint8_t duration) +{ + int ret; + struct usb_setup_packet *setup; + struct usbh_hid *hid_class = (struct usbh_hid *)hport->config.intf[intf].priv; + + setup = hid_class->setup; + + if (hid_class->intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = HID_REQUEST_SET_IDLE; + setup->wValue = report_id; + setup->wIndex = (duration << 8) | intf; + setup->wLength = 0; + + ret = usbh_control_transfer(hport->ep0, setup, NULL); + if (ret < 0) { + return ret; + } + + return 0; +} + +int usbh_hid_get_idle(struct usbh_hubport *hport, uint8_t intf, uint8_t *buffer) +{ + int ret; + struct usb_setup_packet *setup; + struct usbh_hid *hid_class = (struct usbh_hid *)hport->config.intf[intf].priv; + + setup = hid_class->setup; + + if (hid_class->intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = HID_REQUEST_GET_IDLE; + setup->wValue = 0; + setup->wIndex = intf; + setup->wLength = 1; + + ret = usbh_control_transfer(hport->ep0, setup, buffer); + if (ret < 0) { + return ret; + } + + return 0; +} + +USB_NOCACHE_RAM_SECTION uint8_t report_buffer[128]; + +void usbh_hid_callback(void *arg, ssize_t nbytes) +{ + printf("nbytes:%d\r\n", nbytes); +} + +int usbh_hid_connect(struct usbh_hubport *hport, uint8_t intf) +{ + struct usbh_endpoint_cfg ep_cfg = { 0 }; + struct usb_endpoint_descriptor *ep_desc; + char devname[DEV_NAMELEN]; + int ret; + + struct usbh_hid *hid_class = usb_malloc(sizeof(struct usbh_hid)); + if (hid_class == NULL) { + return -1; + } + memset(hid_class, 0, sizeof(struct usbh_hid)); + + usbh_hid_devno_alloc(hid_class); + usbh_hid_mkdevname(hid_class, devname); + + hport->config.intf[intf].priv = hid_class; + + hid_class->setup = usb_iomalloc(sizeof(struct usb_setup_packet)); + hid_class->intf = intf; + + ret = usbh_hid_set_idle(hport, intf, 0, 0); + if (ret < 0) { + return ret; + } + + ret = usbh_hid_get_report_descriptor(hport, intf, report_buffer); + if (ret < 0) { + return ret; + } + + for (uint8_t i = 0; i < hport->config.intf[intf].intf_desc.bNumEndpoints; i++) { + ep_desc = &hport->config.intf[intf].ep[i].ep_desc; + ep_cfg.ep_addr = ep_desc->bEndpointAddress; + ep_cfg.ep_type = ep_desc->bmAttributes & USB_ENDPOINT_TYPE_MASK; + ep_cfg.ep_mps = ep_desc->wMaxPacketSize; + ep_cfg.ep_interval = ep_desc->bInterval; + ep_cfg.hport = hport; + if (ep_desc->bEndpointAddress & 0x80) { + usbh_ep_alloc(&hid_class->intin, &ep_cfg); + } else { + usbh_ep_alloc(&hid_class->intout, &ep_cfg); + } + } + + USB_LOG_INFO("Register HID Class:%s\r\n", devname); + + ret = usbh_ep_intr_async_transfer(hid_class->intin, report_buffer, 128, usbh_hid_callback, NULL); + if (ret < 0) { + return ret; + } +#if 0 + ret = usbh_ep_intr_transfer(hid_class->intin, report_buffer, 128); + if (ret < 0) { + return ret; + } + USB_LOG_INFO("recv len:%d\r\n", ret); +#endif + return 0; +} + +int usbh_hid_disconnect(struct usbh_hubport *hport, uint8_t intf) +{ + char devname[DEV_NAMELEN]; + int ret = 0; + + struct usbh_hid *hid_class = (struct usbh_hid *)hport->config.intf[intf].priv; + + if (hid_class) { + usbh_hid_devno_free(hid_class); + usbh_hid_mkdevname(hid_class, devname); + + if (hid_class->intin) { + ret = usb_ep_cancel(hid_class->intin); + if (ret < 0) { + } + usbh_ep_free(hid_class->intin); + } + + if (hid_class->intout) { + ret = usb_ep_cancel(hid_class->intout); + if (ret < 0) { + } + usbh_ep_free(hid_class->intout); + } + if (hid_class->setup) + usb_iofree(hid_class->setup); + + usb_free(hid_class); + hport->config.intf[intf].priv = NULL; + + USB_LOG_INFO("Unregister HID Class:%s\r\n", devname); + } + + return ret; +} + +const struct usbh_class_driver hid_class_driver = { + .driver_name = "hid", + .connect = usbh_hid_connect, + .disconnect = usbh_hid_disconnect +}; \ No newline at end of file diff --git a/class/hid/usbh_hid.h b/class/hid/usbh_hid.h new file mode 100644 index 00000000..7d01f100 --- /dev/null +++ b/class/hid/usbh_hid.h @@ -0,0 +1,45 @@ +/** + * @file usbh_hid.h + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _USBH_HID_H +#define _USBH_HID_H + +#include "usb_hid.h" + +struct usbh_hid { + struct usb_setup_packet *setup; + uint8_t intf; /* interface number */ + uint8_t minor; + usbh_epinfo_t intin; /* INTR IN endpoint */ + usbh_epinfo_t intout; /* INTR OUT endpoint */ +}; + +extern const struct usbh_class_driver hid_class_driver; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/class/hub/usbh_hub.c b/class/hub/usbh_hub.c new file mode 100644 index 00000000..992b6aef --- /dev/null +++ b/class/hub/usbh_hub.c @@ -0,0 +1,488 @@ +/** + * @file usbh_hub.c + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#include "usbh_core.h" +#include "usbh_hub.h" + +#define DEV_FORMAT "/dev/hub%d" +#define DEV_NAMELEN 16 + +static uint32_t g_devinuse = 0; + +usb_slist_t hub_class_head = USB_SLIST_OBJECT_INIT(hub_class_head); + +USB_NOCACHE_RAM_SECTION uint8_t int_buffer[6][USBH_HUB_INTIN_BUFSIZE]; +extern void usbh_external_hport_connect(struct usbh_hubport *hport); +extern void usbh_external_hport_disconnect(struct usbh_hubport *hport); +extern void usbh_hport_activate(struct usbh_hubport *hport); +extern void usbh_hport_deactivate(struct usbh_hubport *hport); + +static void usbh_external_hub_callback(void *arg, ssize_t nbytes); + +static inline void usbh_hub_register(struct usbh_hub *hub) +{ + usb_slist_add_tail(&hub_class_head, &hub->list); +} + +static inline void usbh_hub_unregister(struct usbh_hub *hub) +{ + usb_slist_remove(&hub_class_head, &hub->list); +} + +/**************************************************************************** + * Name: usbh_hub_devno_alloc + * + * Description: + * Allocate a unique /dev/hub[n] minor number in the range 2-31. + * + ****************************************************************************/ + +static int usbh_hub_devno_alloc(struct usbh_hub *hub) +{ + uint32_t flags; + int devno; + + flags = usb_osal_enter_critical_section(); + for (devno = 2; devno < 32; devno++) { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) { + g_devinuse |= bitno; + hub->index = devno; + usb_osal_leave_critical_section(flags); + return 0; + } + } + + usb_osal_leave_critical_section(flags); + return -EMFILE; +} + +/**************************************************************************** + * Name: usbh_hub_devno_free + * + * Description: + * Free a /dev/hub[n] minor number so that it can be used. + * + ****************************************************************************/ + +static void usbh_hub_devno_free(struct usbh_hub *hub) +{ + int devno = hub->index; + + if (devno >= 2 && devno < 32) { + uint32_t flags = usb_osal_enter_critical_section(); + g_devinuse &= ~(1 << devno); + usb_osal_leave_critical_section(flags); + } +} + +/**************************************************************************** + * Name: usbh_hub_mkdevname + * + * Description: + * Format a /dev/hub[n] device name given a minor number. + * + ****************************************************************************/ + +static inline void usbh_hub_mkdevname(struct usbh_hub *hub, char *devname) +{ + snprintf(devname, DEV_NAMELEN, DEV_FORMAT, hub->index); +} + +int usbh_hub_get_hub_descriptor(struct usbh_hub *hub, uint8_t *buffer) +{ + struct usb_setup_packet *setup; + + setup = hub->setup; + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = HUB_DESCRIPTOR_TYPE_HUB << 8; + setup->wIndex = 0; + setup->wLength = USB_SIZEOF_HUB_DESC; + + return usbh_control_transfer(hub->parent->ep0, setup, buffer); +} + +int usbh_hub_get_status(struct usbh_hub *hub, uint8_t *buffer) +{ + struct usb_setup_packet *setup; + + setup = hub->setup; + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = HUB_REQUEST_GET_STATUS; + setup->wValue = 0; + setup->wIndex = 0; + setup->wLength = 2; + + return usbh_control_transfer(hub->parent->ep0, setup, buffer); +} + +int usbh_hub_get_portstatus(struct usbh_hub *hub, uint8_t port, struct hub_port_status *port_status) +{ + struct usb_setup_packet *setup; + + setup = hub->setup; + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_OTHER; + setup->bRequest = HUB_REQUEST_GET_STATUS; + setup->wValue = 0; + setup->wIndex = port; + setup->wLength = 4; + + return usbh_control_transfer(hub->parent->ep0, setup, (uint8_t *)port_status); +} + +int usbh_hub_set_feature(struct usbh_hub *hub, uint8_t port, uint8_t feature) +{ + struct usb_setup_packet *setup; + + setup = hub->setup; + + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_OTHER; + setup->bRequest = HUB_REQUEST_SET_FEATURE; + setup->wValue = feature; + setup->wIndex = port; + setup->wLength = 0; + + return usbh_control_transfer(hub->parent->ep0, setup, NULL); +} + +int usbh_hub_clear_feature(struct usbh_hub *hub, uint8_t port, uint8_t feature) +{ + struct usb_setup_packet *setup; + + setup = hub->setup; + + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_OTHER; + setup->bRequest = HUB_REQUEST_CLEAR_FEATURE; + setup->wValue = feature; + setup->wIndex = port; + setup->wLength = 0; + + return usbh_control_transfer(hub->parent->ep0, setup, NULL); +} + +static int parse_hub_descriptor(struct usb_hub_descriptor *desc, uint16_t length) +{ + if (desc->bLength != USB_SIZEOF_HUB_DESC) { + USB_LOG_ERR("invalid device bLength 0x%02x\r\n", desc->bLength); + return -1; + } else if (desc->bDescriptorType != HUB_DESCRIPTOR_TYPE_HUB) { + USB_LOG_ERR("unexpected descriptor 0x%02x\r\n", desc->bDescriptorType); + return -2; + } else { + USB_LOG_INFO("Device Descriptor:\r\n"); + USB_LOG_INFO("bLength: 0x%02x \r\n", desc->bLength); + USB_LOG_INFO("bDescriptorType: 0x%02x \r\n", desc->bDescriptorType); + USB_LOG_INFO("bNbrPorts: 0x%02x \r\n", desc->bNbrPorts); + USB_LOG_INFO("wHubCharacteristics: 0x%04x \r\n", desc->wHubCharacteristics); + USB_LOG_INFO("bPwrOn2PwrGood: 0x%02x \r\n", desc->bPwrOn2PwrGood); + USB_LOG_INFO("bHubContrCurrent: 0x%02x \r\n", desc->bHubContrCurrent); + USB_LOG_INFO("DeviceRemovable: 0x%02x \r\n", desc->DeviceRemovable); + USB_LOG_INFO("PortPwrCtrlMask: 0x%02x \r\n", desc->PortPwrCtrlMask); + } + return 0; +} + +int usbh_hub_connect(struct usbh_hubport *hport, uint8_t intf) +{ + struct usbh_endpoint_cfg ep_cfg = { 0 }; + struct usb_endpoint_descriptor *ep_desc; + char devname[DEV_NAMELEN]; + int ret; + uint8_t *hub_desc_buffer; + struct usbh_hub *hub_class; + + hub_class = usb_malloc(sizeof(struct usbh_hub)); + if (hub_class == NULL) { + return -1; + } + memset(hub_class, 0, sizeof(struct usbh_hub)); + hub_class->setup = usb_iomalloc(sizeof(struct usb_setup_packet)); + hub_class->port_status = usb_iomalloc(sizeof(struct hub_port_status)); + + hub_desc_buffer = usb_iomalloc(32); + + usbh_hub_devno_alloc(hub_class); + usbh_hub_mkdevname(hub_class, devname); + + hub_class->dev_addr = hport->dev_addr; + hub_class->parent = hport; + hport->config.intf[0].priv = hub_class; + + ret = usbh_hub_get_hub_descriptor(hub_class, hub_desc_buffer); + if (ret != 0) { + usb_iofree(hub_desc_buffer); + return ret; + } + + parse_hub_descriptor((struct usb_hub_descriptor *)hub_desc_buffer, USB_SIZEOF_HUB_DESC); + memcpy(&hub_class->hub_desc, hub_desc_buffer, USB_SIZEOF_HUB_DESC); + usb_iofree(hub_desc_buffer); + + hub_class->nports = hub_class->hub_desc.bNbrPorts; + + for (uint8_t port = 1; port <= hub_class->nports; port++) { + hub_class->child[port - 1].port = port; + hub_class->child[port - 1].parent = hub_class; + } + + hub_class->int_buffer = int_buffer[hub_class->index - 2]; + usbh_hub_register(hub_class); + + ep_desc = &hport->config.intf[intf].ep[0].ep_desc; + ep_cfg.ep_addr = ep_desc->bEndpointAddress; + ep_cfg.ep_type = ep_desc->bmAttributes & USB_ENDPOINT_TYPE_MASK; + ep_cfg.ep_mps = ep_desc->wMaxPacketSize; + ep_cfg.ep_interval = ep_desc->bInterval; + ep_cfg.hport = hport; + if (ep_desc->bEndpointAddress & 0x80) { + usbh_ep_alloc(&hub_class->intin, &ep_cfg); + } else { + return -1; + } + + for (uint8_t port = 1; port <= hub_class->nports; port++) { + ret = usbh_hub_set_feature(hub_class, 1, HUB_PORT_FEATURE_POWER); + if (ret < 0) { + return ret; + } + } + + for (uint8_t port = 1; port <= hub_class->nports; port++) { + ret = usbh_hub_get_portstatus(hub_class, port, hub_class->port_status); + USB_LOG_INFO("Port:%d, status:0x%02x, change:0x%02x\r\n", port, hub_class->port_status->wPortStatus, hub_class->port_status->wPortChange); + if (ret < 0) { + return ret; + } + } + + USB_LOG_INFO("Register HUB Class:%s\r\n", devname); + + ret = usbh_ep_intr_async_transfer(hub_class->intin, hub_class->int_buffer, USBH_HUB_INTIN_BUFSIZE, usbh_external_hub_callback, hub_class); + return 0; +} + +int usbh_hub_disconnect(struct usbh_hubport *hport, uint8_t intf) +{ + struct usbh_hubport *child; + char devname[DEV_NAMELEN]; + int ret = 0; + + struct usbh_hub *hub_class = (struct usbh_hub *)hport->config.intf[intf].priv; + + if (hub_class) { + usbh_hub_devno_free(hub_class); + usbh_hub_mkdevname(hub_class, devname); + + if (hub_class->intin) { + ret = usb_ep_cancel(hub_class->intin); + if (ret < 0) { + } + usbh_ep_free(hub_class->intin); + } + + if (hub_class->setup) + usb_iofree(hub_class->setup); + if (hub_class->port_status) + usb_iofree(hub_class->port_status); + + for (uint8_t port = 1; port <= hub_class->nports; port++) { + child = &hub_class->child[port - 1]; + usbh_hport_deactivate(child); + for (uint8_t i = 0; i < child->config.config_desc.bNumInterfaces; i++) { + if (child->config.intf[i].class_driver && child->config.intf[i].class_driver->disconnect) { + ret = CLASS_DISCONNECT(child, i); + } + } + + child->config.config_desc.bNumInterfaces = 0; + child->parent = NULL; + } + + usbh_hub_unregister(hub_class); + usb_free(hub_class); + hport->config.intf[intf].priv = NULL; + + USB_LOG_INFO("Unregister HUB Class:%s\r\n", devname); + } + return ret; +} + +static void usbh_extern_hub_psc_event(void *arg) +{ + struct usbh_hub *hub_class; + struct usbh_hubport *connport; + uint8_t port_change; + uint16_t status; + uint16_t change; + uint16_t mask; + uint16_t feat; + uint32_t flags; + int ret; + + hub_class = (struct usbh_hub *)arg; + + /* Has the hub been disconnected? */ + if (!hub_class->parent->connected) { + return; + } + + port_change = hub_class->int_buffer[0]; + USB_LOG_DBG("port_change:0x%02x\r\n", port_change); + + /* Check for status change on any port */ + for (uint8_t port = USBH_HUB_PORT_START_INDEX; port <= hub_class->nports; port++) { + /* Check if port status has changed */ + if ((port_change & (1 << port)) == 0) { + continue; + } + USB_LOG_DBG("Port %d change\r\n", port); + + /* Port status changed, check what happened */ + port_change &= ~(1 << port); + + /* Read hub port status */ + ret = usbh_hub_get_portstatus(hub_class, port, hub_class->port_status); + if (ret < 0) { + USB_LOG_ERR("Failed to read port:%d status, errorcode: %d\r\n", port, ret); + continue; + } + status = hub_class->port_status->wPortStatus; + change = hub_class->port_status->wPortChange; + + USB_LOG_DBG("Port:%d, status:0x%02x, change:0x%02x\r\n", port, status, change); + + /* First, clear all change bits */ + mask = 1; + feat = HUB_PORT_FEATURE_C_CONNECTION; + while (change) { + if (change & mask) { + ret = usbh_hub_clear_feature(hub_class, port, feat); + if (ret < 0) { + USB_LOG_ERR("Failed to clear port:%d, change mask:%04x, errorcode:%d\r\n", port, mask, ret); + } + change &= (~mask); + } + mask <<= 1; + feat++; + } + + change = hub_class->port_status->wPortChange; + + /* Handle connect or disconnect, no power management */ + if (change & HUB_PORT_STATUS_C_CONNECTION) { + ret = usbh_hub_get_portstatus(hub_class, port, hub_class->port_status); + if (ret < 0) { + USB_LOG_ERR("Failed to read port:%d status, errorcode: %d\r\n", port, ret); + } + status = hub_class->port_status->wPortStatus; + change = hub_class->port_status->wPortChange; + + if (status & HUB_PORT_STATUS_CONNECTION) { + /* Device connected to a port on the hub */ + //USB_LOG_INFO("Connection on port:%d\n", port); + + ret = usbh_hub_set_feature(hub_class, port, HUB_PORT_FEATURE_RESET); + if (ret < 0) { + USB_LOG_ERR("Failed to reset port:%d,errorcode:%d\r\n", port, ret); + continue; + } + + usb_osal_msleep(100); + + ret = usbh_hub_get_portstatus(hub_class, port, hub_class->port_status); + if (ret < 0) { + USB_LOG_ERR("Failed to read port:%d status, errorcode: %d\r\n", port, ret); + continue; + } + status = hub_class->port_status->wPortStatus; + change = hub_class->port_status->wPortChange; + + USB_LOG_DBG("Port:%d, status:0x%02x, change:0x%02x after reset\r\n", port, status, change); + + if ((status & HUB_PORT_STATUS_RESET) == 0 && (status & HUB_PORT_STATUS_ENABLE) != 0) { + if (change & HUB_PORT_STATUS_C_RESET) { + ret = usbh_hub_clear_feature(hub_class, port, HUB_PORT_FEATURE_C_RESET); + if (ret < 0) { + USB_LOG_ERR("Failed to clear port:%d reset change, errorcode: %d\r\n", port, ret); + } + } + connport = &hub_class->child[port - 1]; + + if (status & HUB_PORT_STATUS_HIGH_SPEED) { + connport->speed = USB_SPEED_HIGH; + } else if (status & HUB_PORT_STATUS_LOW_SPEED) { + connport->speed = USB_SPEED_LOW; + } else { + connport->speed = USB_SPEED_FULL; + } + + /* Device connected from a port on the hub, wakeup psc thread. */ + usbh_external_hport_connect(connport); + + } else { + USB_LOG_ERR("Failed to enable port:%d\r\n", port); + continue; + } + } else { + /* Device disconnected from a port on the hub, wakeup psc thread. */ + connport = &hub_class->child[port - 1]; + usbh_external_hport_disconnect(connport); + } + } else { + USB_LOG_WRN("status %04x change %04x not handled\r\n", status, change); + } + } + + /* Check for hub status change */ + + if ((port_change & 1) != 0) { + /* Hub status changed */ + USB_LOG_WRN("Hub status changed, not handled\n"); + } + flags = usb_osal_enter_critical_section(); + if (hub_class->parent->connected) { + ret = usbh_ep_intr_async_transfer(hub_class->intin, hub_class->int_buffer, USBH_HUB_INTIN_BUFSIZE, usbh_external_hub_callback, hub_class); + } + usb_osal_leave_critical_section(flags); +} + +static void usbh_external_hub_callback(void *arg, ssize_t nbytes) +{ + struct usbh_hub *hub_class = (struct usbh_hub *)arg; + uint32_t delay = 0; + if (nbytes < 0) { + hub_class->int_buffer[0] = 0; + delay = 100; + } + if (hub_class->parent->connected) { + usb_workqueue_submit(&g_lpworkq, &hub_class->work, usbh_extern_hub_psc_event, (void *)hub_class, delay); + } +} + +const struct usbh_class_driver hub_class_driver = { + .driver_name = "hub", + .connect = usbh_hub_connect, + .disconnect = usbh_hub_disconnect +}; \ No newline at end of file diff --git a/class/hub/usbh_hub.h b/class/hub/usbh_hub.h new file mode 100644 index 00000000..98c46542 --- /dev/null +++ b/class/hub/usbh_hub.h @@ -0,0 +1,42 @@ +/** + * @file usbh_hub.h + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _USBH_HUB_H_ +#define _USBH_HUB_H_ + +#include "usb_hub.h" + +#define USBH_HUB_MAX_PORTS 4 +/* Maximum size of an interrupt IN transfer */ +#define USBH_HUB_INTIN_BUFSIZE ((USBH_HUB_MAX_PORTS + 8) >> 3) + +extern const struct usbh_class_driver hub_class_driver; +extern usb_slist_t hub_class_head; +extern usb_osal_thread_t hub_thread; +#ifdef __cplusplus +extern "C" { +#endif +int usbh_hub_initialize(void); +#ifdef __cplusplus +} +#endif + +#endif /* _USBH_HUB_H_ */ diff --git a/class/msc/usbh_msc.c b/class/msc/usbh_msc.c new file mode 100644 index 00000000..90da54f1 --- /dev/null +++ b/class/msc/usbh_msc.c @@ -0,0 +1,359 @@ +/** + * @file usbh_msc.c + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#include "usbh_core.h" +#include "usbh_msc.h" + +#define DEV_FORMAT "/dev/sd%c" +#define DEV_NAMELEN 16 + +static uint32_t g_devinuse = 0; + +/**************************************************************************** + * Name: usbh_msc_devno_alloc + * + * Description: + * Allocate a unique /dev/ttyACM[n] minor number in the range 0-31. + * + ****************************************************************************/ + +static int usbh_msc_devno_alloc(struct usbh_msc *priv) +{ + uint32_t flags; + int devno; + + flags = usb_osal_enter_critical_section(); + for (devno = 0; devno < 26; devno++) { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) { + g_devinuse |= bitno; + priv->sdchar = 'a' + devno; + usb_osal_leave_critical_section(flags); + return 0; + } + } + + usb_osal_leave_critical_section(flags); + return -EMFILE; +} + +/**************************************************************************** + * Name: usbh_msc_devno_free + * + * Description: + * Free a /dev/sd[n] minor number so that it can be used. + * + ****************************************************************************/ + +static void usbh_msc_devno_free(struct usbh_msc *priv) +{ + int devno = priv->sdchar - 'a'; + + if (devno >= 0 && devno < 26) { + uint32_t flags = usb_osal_enter_critical_section(); + g_devinuse &= ~(1 << devno); + usb_osal_leave_critical_section(flags); + } +} + +/**************************************************************************** + * Name: usbh_msc_mkdevname + * + * Description: + * Format a /dev/sd[n] device name given a minor number. + * + ****************************************************************************/ + +static inline void usbh_msc_mkdevname(struct usbh_msc *priv, char *devname) +{ + snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->sdchar); +} + +static int usbh_msc_get_maxlun(struct usbh_hubport *hport, uint8_t intf, uint8_t *buffer) +{ + struct usb_setup_packet *setup; + struct usbh_msc *msc_class = (struct usbh_msc *)hport->config.intf[intf].priv; + + setup = msc_class->setup; + + if (msc_class->intf != intf) { + return -1; + } + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE; + setup->bRequest = MSC_REQUEST_GET_MAX_LUN; + setup->wValue = 0; + setup->wIndex = intf; + setup->wLength = 1; + + return usbh_control_transfer(hport->ep0, setup, buffer); +} + +static void usbh_msc_cbw_dump(struct CBW *cbw) +{ + int i; + + USB_LOG_INFO("CBW:\r\n"); + USB_LOG_INFO(" signature: 0x%08x\r\n", (unsigned int)cbw->dSignature); + USB_LOG_INFO(" tag: 0x%08x\r\n", (unsigned int)cbw->dTag); + USB_LOG_INFO(" datlen: 0x%08x\r\n", (unsigned int)cbw->dDataLength); + USB_LOG_INFO(" flags: 0x%02x\r\n", cbw->bmFlags); + USB_LOG_INFO(" lun: 0x%02x\r\n", cbw->bLUN); + USB_LOG_INFO(" cblen: 0x%02x\r\n", cbw->bCBLength); + + USB_LOG_INFO("CB:\r\n"); + for (i = 0; i < cbw->bCBLength; i += 8) { + USB_LOG_INFO(" 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\r\n", + cbw->CB[i], cbw->CB[i + 1], cbw->CB[i + 2], + cbw->CB[i + 3], cbw->CB[i + 4], cbw->CB[i + 5], + cbw->CB[i + 6], cbw->CB[i + 7]); + } +} + +static void usbh_msc_csw_dump(struct CSW *csw) +{ + USB_LOG_INFO("CSW:\r\n"); + USB_LOG_INFO(" signature: 0x%08x\r\n", (unsigned int)csw->dSignature); + USB_LOG_INFO(" tag: 0x%08x\r\n", (unsigned int)csw->dTag); + USB_LOG_INFO(" residue: 0x%08x\r\n", (unsigned int)csw->dDataResidue); + USB_LOG_INFO(" status: 0x%02x\r\n", csw->bStatus); +} + +static inline int usbh_msc_scsi_testunitready(struct usbh_msc *msc_class) +{ + int nbytes; + struct CBW *cbw; + + /* Construct the CBW */ + cbw = (struct CBW *)msc_class->tx_buffer; + memset(cbw, 0, USB_SIZEOF_MSC_CBW); + cbw->dSignature = MSC_CBW_Signature; + + cbw->bCBLength = 0x06; + cbw->CB[0] = SCSI_TEST_UNIT_READY; + /* Send the CBW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkout, (uint8_t *)cbw, USB_SIZEOF_MSC_CBW); + if (nbytes >= 0) { + /* Receive the CSW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, USB_SIZEOF_MSC_CSW); + if (nbytes >= 0) { + usbh_msc_csw_dump((struct CSW *)msc_class->tx_buffer); + } + } + return nbytes < 0 ? (int)nbytes : 0; +} + +static inline int usbh_msc_scsi_requestsense(struct usbh_msc *msc_class) +{ + int nbytes; + struct CBW *cbw; + + /* Construct the CBW */ + cbw = (struct CBW *)msc_class->tx_buffer; + memset(cbw, 0, USB_SIZEOF_MSC_CBW); + cbw->dSignature = MSC_CBW_Signature; + + cbw->bmFlags = 0x80; + cbw->bCBLength = 0x06; + cbw->dDataLength = 18; + cbw->CB[0] = SCSI_REQUEST_SENSE; + cbw->CB[4] = 18; + /* Send the CBW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkout, (uint8_t *)cbw, USB_SIZEOF_MSC_CBW); + if (nbytes >= 0) { + /* Receive the sense data response */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, 18); + if (nbytes >= 0) { + /* Receive the CSW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, USB_SIZEOF_MSC_CSW); + if (nbytes >= 0) { + usbh_msc_csw_dump((struct CSW *)msc_class->tx_buffer); + } + } + } + return nbytes < 0 ? (int)nbytes : 0; +} + +static inline int usbh_msc_scsi_inquiry(struct usbh_msc *msc_class) +{ + int nbytes; + struct CBW *cbw; + + /* Construct the CBW */ + cbw = (struct CBW *)msc_class->tx_buffer; + memset(cbw, 0, USB_SIZEOF_MSC_CBW); + cbw->dSignature = MSC_CBW_Signature; + + cbw->dDataLength = 36; + cbw->bmFlags = 0x80; + cbw->bCBLength = 6; + cbw->CB[0] = SCSI_INQUIRY; + cbw->CB[4] = 36; + + /* Send the CBW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkout, (uint8_t *)cbw, USB_SIZEOF_MSC_CBW); + if (nbytes >= 0) { + /* Receive the sense data response */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, 36); + if (nbytes >= 0) { + /* Receive the CSW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, USB_SIZEOF_MSC_CSW); + if (nbytes >= 0) { + usbh_msc_csw_dump((struct CSW *)msc_class->tx_buffer); + } + } + } + return nbytes < 0 ? (int)nbytes : 0; +} + +static inline int usbh_msc_scsi_readcapacity10(struct usbh_msc *msc_class) +{ + int nbytes; + struct CBW *cbw; + + /* Construct the CBW */ + cbw = (struct CBW *)msc_class->tx_buffer; + memset(cbw, 0, USB_SIZEOF_MSC_CBW); + cbw->dSignature = MSC_CBW_Signature; + + cbw->dDataLength = 8; + cbw->bmFlags = 0x80; + cbw->bCBLength = 10; + cbw->CB[0] = SCSI_READ_CAPACITY10; + + /* Send the CBW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkout, (uint8_t *)cbw, USB_SIZEOF_MSC_CBW); + if (nbytes >= 0) { + /* Receive the sense data response */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, 8); + if (nbytes >= 0) { + /* Save the capacity information */ + msc_class->blocknum = GET_BE32(&msc_class->tx_buffer[0]) + 1; + msc_class->blocksize = GET_BE32(&msc_class->tx_buffer[4]); + USB_LOG_INFO("capacity info:\r\n"); + USB_LOG_INFO("block num:%d,block size:%d\r\n", (unsigned int)msc_class->blocknum, (unsigned int)msc_class->blocksize); + /* Receive the CSW */ + nbytes = usbh_ep_bulk_transfer(msc_class->bulkin, msc_class->tx_buffer, USB_SIZEOF_MSC_CSW); + if (nbytes >= 0) { + usbh_msc_csw_dump((struct CSW *)msc_class->tx_buffer); + } + } + } + return nbytes < 0 ? (int)nbytes : 0; +} + +int usbh_msc_connect(struct usbh_hubport *hport, uint8_t intf) +{ + struct usbh_endpoint_cfg ep_cfg = { 0 }; + struct usb_endpoint_descriptor *ep_desc; + + char devname[DEV_NAMELEN]; + int ret; + + struct usbh_msc *msc_class = usb_malloc(sizeof(struct usbh_msc)); + if (msc_class == NULL) { + return -ENOMEM; + } + + memset(msc_class, 0, sizeof(struct usbh_msc)); + + usbh_msc_devno_alloc(msc_class); + usbh_msc_mkdevname(msc_class, devname); + + hport->config.intf[intf].priv = msc_class; + + msc_class->setup = usb_iomalloc(sizeof(struct usb_setup_packet)); + msc_class->tx_buffer = usb_iomalloc(128); + + ret = usbh_msc_get_maxlun(hport, intf, msc_class->tx_buffer); + if (ret < 0) { + return ret; + } + + USB_LOG_INFO("Get max LUN:%u\r\n", msc_class->tx_buffer[0]); + + for (uint8_t i = 0; i < hport->config.intf[intf].intf_desc.bNumEndpoints; i++) { + ep_desc = &hport->config.intf[intf].ep[i].ep_desc; + + ep_cfg.ep_addr = ep_desc->bEndpointAddress; + ep_cfg.ep_type = ep_desc->bmAttributes & USB_ENDPOINT_TYPE_MASK; + ep_cfg.ep_mps = ep_desc->wMaxPacketSize; + ep_cfg.ep_interval = ep_desc->bInterval; + ep_cfg.hport = hport; + if (ep_desc->bEndpointAddress & 0x80) { + usbh_ep_alloc(&msc_class->bulkin, &ep_cfg); + } else { + usbh_ep_alloc(&msc_class->bulkout, &ep_cfg); + } + } + + USB_LOG_INFO("Register MSC Class:%s\r\n", devname); + + ret = usbh_msc_scsi_testunitready(msc_class); + ret = usbh_msc_scsi_inquiry(msc_class); + ret = usbh_msc_scsi_readcapacity10(msc_class); + + return ret; +} + +int usbh_msc_disconnect(struct usbh_hubport *hport, uint8_t intf) +{ + char devname[DEV_NAMELEN]; + int ret = 0; + + struct usbh_msc *msc_class = (struct usbh_msc *)hport->config.intf[intf].priv; + + if (msc_class) { + usbh_msc_devno_free(msc_class); + usbh_msc_mkdevname(msc_class, devname); + + if (msc_class->bulkin) { + ret = usb_ep_cancel(msc_class->bulkin); + if (ret < 0) { + } + usbh_ep_free(msc_class->bulkin); + } + + if (msc_class->bulkout) { + ret = usb_ep_cancel(msc_class->bulkout); + if (ret < 0) { + } + usbh_ep_free(msc_class->bulkout); + } + + if (msc_class->setup) + usb_iofree(msc_class->setup); + + usb_free(msc_class); + + hport->config.intf[intf].priv = NULL; + + USB_LOG_INFO("Unregister MSC Class:%s\r\n", devname); + } + + return ret; +} + +const struct usbh_class_driver msc_class_driver = { + .driver_name = "msc", + .connect = usbh_msc_connect, + .disconnect = usbh_msc_disconnect +}; \ No newline at end of file diff --git a/class/msc/usbh_msc.h b/class/msc/usbh_msc.h new file mode 100644 index 00000000..ccc7806e --- /dev/null +++ b/class/msc/usbh_msc.h @@ -0,0 +1,49 @@ +/** + * @file usbh_msc.h + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _USBH_MSC_H +#define _USBH_MSC_H + +#include "usb_msc.h" +#include "usb_scsi.h" + +struct usbh_msc { + struct usb_setup_packet *setup; + uint8_t intf; /* Data interface number */ + uint8_t sdchar; + usbh_epinfo_t bulkin; /* Bulk IN endpoint */ + usbh_epinfo_t bulkout; /* Bulk OUT endpoint */ + uint8_t *tx_buffer; + uint32_t blocknum; /* Number of blocks on the USB mass storage device */ + uint16_t blocksize; /* Block size of USB mass storage device */ +}; + +extern const struct usbh_class_driver msc_class_driver; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/core/usbh_core.c b/core/usbh_core.c new file mode 100644 index 00000000..41def3bb --- /dev/null +++ b/core/usbh_core.c @@ -0,0 +1,989 @@ +/** + * @file usbh_core.c + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#include "usbh_core.h" +#include "usbh_cdc_acm.h" +#include "usbh_hid.h" +#include "usbh_msc.h" + +static const char *speed_table[] = { "error speed", "low speed", "full speed", "high speed" }; + +static const struct usbh_class_driver *usbh_find_class_driver(uint8_t class, uint8_t subcalss, uint8_t protocol, uint16_t vid, uint16_t pid); +void usbh_hport_activate(struct usbh_hubport *hport); +void usbh_hport_deactivate(struct usbh_hubport *hport); + +/* general descriptor field offsets */ +#define DESC_bLength 0 /** Length offset */ +#define DESC_bDescriptorType 1 /** Descriptor type offset */ + +#define USB_DEV_ADDR_MAX 0x7f +#define USB_DEV_ADDR_MARK_OFFSET 5 +#define USB_DEV_ADDR_MARK_MASK 0x1f + +struct usbh_devaddr_priv { + /** + * alloctab[0]:addr from 0~31 + * alloctab[1]:addr from 32~63 + * alloctab[2]:addr from 64~95 + * alloctab[3]:addr from 96~127 + * + */ + uint8_t next; /* Next device address */ + uint32_t alloctab[4]; /* Bit allocation table */ +}; + +struct usbh_roothubport_priv { + struct usbh_hubport hport; /* Common hub port definitions */ + struct usbh_devaddr_priv devgen; /* Address generation data */ +}; + +struct usbh_core_priv { + struct usbh_roothubport_priv rhport[CONFIG_USBHOST_RHPORTS]; + volatile struct usbh_hubport *active_hport; /* Used to pass external hub port events */ + volatile bool pscwait; /* TRUE: Thread is waiting for port status change event */ + usb_osal_sem_t pscsem; /* Semaphore to wait for a port event */ +} usbh_core_cfg; + +static inline struct usbh_roothubport_priv *usbh_find_roothub_port(struct usbh_hubport *hport) +{ + while (hport->parent != NULL) { + hport = hport->parent->parent; + } + return (struct usbh_roothubport_priv *)hport; +} + +static int usbh_allocate_devaddr(struct usbh_devaddr_priv *devgen) +{ + uint8_t startaddr = devgen->next; + uint8_t devaddr; + int index; + int bitno; + + /* Loop until we find a valid device address */ + + for (;;) { + /* Try the next device address */ + + devaddr = devgen->next; + if (devgen->next >= 0x7f) { + devgen->next = 1; + } else { + devgen->next++; + } + + /* Is this address already allocated? */ + + index = devaddr >> 5; + bitno = devaddr & 0x1f; + if ((devgen->alloctab[index] & (1 << bitno)) == 0) { + /* No... allocate it now */ + + devgen->alloctab[index] |= (1 << bitno); + return (int)devaddr; + } + + /* This address has already been allocated. The following logic will + * prevent (unexpected) infinite loops. + */ + + if (startaddr == devaddr) { + /* We are back where we started... the are no free device address */ + + return -ENOMEM; + } + } +} + +static int usbh_free_devaddr(struct usbh_devaddr_priv *devgen, uint8_t devaddr) +{ + int index; + int bitno; + + if ((devaddr > 0) && (devaddr < USB_DEV_ADDR_MAX)) { + index = devaddr >> USB_DEV_ADDR_MARK_OFFSET; + bitno = devaddr & USB_DEV_ADDR_MARK_MASK; + + /* Free the address by clearing the associated bit in the alloctab[]; */ + if ((devgen->alloctab[index] |= (1 << bitno)) != 0) { + devgen->alloctab[index] &= ~(1 << bitno); + } else { + return -1; + } + /* Reset the next pointer if the one just released has a lower value */ + + if (devaddr < devgen->next) { + devgen->next = devaddr; + } + } + + return 0; +} + +static int usbh_devaddr_create(struct usbh_hubport *hport) +{ + struct usbh_roothubport_priv *rhport; + rhport = usbh_find_roothub_port(hport); + + return usbh_allocate_devaddr(&rhport->devgen); +} + +static int usbh_devaddr_destroy(struct usbh_hubport *hport, uint8_t dev_addr) +{ + struct usbh_roothubport_priv *rhport; + rhport = usbh_find_roothub_port(hport); + + return usbh_free_devaddr(&rhport->devgen, dev_addr); +} + +static int parse_device_descriptor(struct usbh_hubport *hport, struct usb_device_descriptor *desc, uint16_t length) +{ + if (desc->bLength != USB_SIZEOF_DEVICE_DESC) { + USB_LOG_ERR("invalid device bLength 0x%02x\r\n", desc->bLength); + return -EINVAL; + } else if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_DEVICE) { + USB_LOG_ERR("unexpected descriptor 0x%02x\r\n", desc->bDescriptorType); + return -EINVAL; + } else { + if (length <= 8) { + return 0; + } +#if 0 + USB_LOG_DBG("Device Descriptor:\r\n"); + USB_LOG_DBG("bLength: 0x%02x \r\n", desc->bLength); + USB_LOG_DBG("bDescriptorType: 0x%02x \r\n", desc->bDescriptorType); + USB_LOG_DBG("bcdUSB: 0x%04x \r\n", desc->bcdUSB); + USB_LOG_DBG("bDeviceClass: 0x%02x \r\n", desc->bDeviceClass); + USB_LOG_DBG("bDeviceSubClass: 0x%02x \r\n", desc->bDeviceSubClass); + USB_LOG_DBG("bDeviceProtocol: 0x%02x \r\n", desc->bDeviceProtocol); + USB_LOG_DBG("bMaxPacketSize0: 0x%02x \r\n", desc->bMaxPacketSize0); + USB_LOG_DBG("idVendor: 0x%04x \r\n", desc->idVendor); + USB_LOG_DBG("idProduct: 0x%04x \r\n", desc->idProduct); + USB_LOG_DBG("bcdDevice: 0x%04x \r\n", desc->bcdDevice); + USB_LOG_DBG("iManufacturer: 0x%02x \r\n", desc->iManufacturer); + USB_LOG_DBG("iProduct: 0x%02x \r\n", desc->iProduct); + USB_LOG_DBG("iSerialNumber: 0x%02x \r\n", desc->iSerialNumber); + USB_LOG_DBG("bNumConfigurations: 0x%02x\r\n", desc->bNumConfigurations); +#endif + hport->device_desc.bLength = desc->bLength; + hport->device_desc.bDescriptorType = desc->bDescriptorType; + hport->device_desc.bcdUSB = desc->bcdUSB; + hport->device_desc.bDeviceClass = desc->bDeviceClass; + hport->device_desc.bDeviceSubClass = desc->bDeviceSubClass; + hport->device_desc.bDeviceProtocol = desc->bDeviceProtocol; + hport->device_desc.bMaxPacketSize0 = desc->bMaxPacketSize0; + hport->device_desc.idVendor = desc->idVendor; + hport->device_desc.idProduct = desc->idProduct; + hport->device_desc.bcdDevice = desc->bcdDevice; + hport->device_desc.iManufacturer = desc->iManufacturer; + hport->device_desc.iProduct = desc->iProduct; + hport->device_desc.iSerialNumber = desc->iSerialNumber; + hport->device_desc.bNumConfigurations = desc->bNumConfigurations; + } + return 0; +} + +static int parse_config_descriptor(struct usbh_hubport *hport, struct usb_configuration_descriptor *desc, uint16_t length) +{ + uint32_t total_len = 0; + uint8_t ep_num = 0; + uint8_t intf_num = 0; + uint8_t *p = (uint8_t *)desc; + if (desc->bLength != USB_SIZEOF_CONFIG_DESC) { + USB_LOG_ERR("invalid device bLength 0x%02x\r\n", desc->bLength); + return -EINVAL; + } else if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_CONFIGURATION) { + USB_LOG_ERR("unexpected descriptor 0x%02x\r\n", desc->bDescriptorType); + return -EINVAL; + } else { + if (length <= USB_SIZEOF_CONFIG_DESC) { + return 0; + } +#if 0 + USB_LOG_DBG("Config Descriptor:\r\n"); + USB_LOG_DBG("bLength: 0x%02x \r\n", desc->bLength); + USB_LOG_DBG("bDescriptorType: 0x%02x \r\n", desc->bDescriptorType); + USB_LOG_DBG("wTotalLength: 0x%04x \r\n", desc->wTotalLength); + USB_LOG_DBG("bNumInterfaces: 0x%02x \r\n", desc->bNumInterfaces); + USB_LOG_DBG("bConfigurationValue: 0x%02x \r\n", desc->bConfigurationValue); + USB_LOG_DBG("iConfiguration: 0x%02x \r\n", desc->iConfiguration); + USB_LOG_DBG("bmAttributes: 0x%02x \r\n", desc->bmAttributes); + USB_LOG_DBG("bMaxPower: 0x%02x \r\n", desc->bMaxPower); +#endif + hport->config.config_desc.bLength = desc->bLength; + hport->config.config_desc.bDescriptorType = desc->bDescriptorType; + hport->config.config_desc.wTotalLength = desc->wTotalLength; + hport->config.config_desc.bNumInterfaces = desc->bNumInterfaces; + hport->config.config_desc.bConfigurationValue = desc->bConfigurationValue; + hport->config.config_desc.iConfiguration = desc->iConfiguration; + hport->config.config_desc.iConfiguration = desc->iConfiguration; + hport->config.config_desc.bmAttributes = desc->bmAttributes; + hport->config.config_desc.bMaxPower = desc->bMaxPower; + + if (length > USB_SIZEOF_CONFIG_DESC) { + while (p[DESC_bLength] && (total_len < desc->wTotalLength) && (intf_num < desc->bNumInterfaces)) { + p += p[DESC_bLength]; + total_len += p[DESC_bLength]; + if (p[DESC_bDescriptorType] == USB_DESCRIPTOR_TYPE_INTERFACE) { + struct usb_interface_descriptor *intf_desc = (struct usb_interface_descriptor *)p; +#if 0 + USB_LOG_DBG("Interface Descriptor:\r\n"); + USB_LOG_DBG("bLength: 0x%02x \r\n", intf_desc->bLength); + USB_LOG_DBG("bDescriptorType: 0x%02x \r\n", intf_desc->bDescriptorType); + USB_LOG_DBG("bInterfaceNumber: 0x%02x \r\n", intf_desc->bInterfaceNumber); + USB_LOG_DBG("bAlternateSetting: 0x%02x \r\n", intf_desc->bAlternateSetting); + USB_LOG_DBG("bNumEndpoints: 0x%02x \r\n", intf_desc->bNumEndpoints); + USB_LOG_DBG("bInterfaceClass: 0x%02x \r\n", intf_desc->bInterfaceClass); + USB_LOG_DBG("bInterfaceSubClass: 0x%02x \r\n", intf_desc->bInterfaceSubClass); + USB_LOG_DBG("bInterfaceProtocol: 0x%02x \r\n", intf_desc->bInterfaceProtocol); + USB_LOG_DBG("iInterface: 0x%02x \r\n", intf_desc->iInterface); +#endif + memset(&hport->config.intf[intf_num], 0, sizeof(struct usbh_interface)); + + hport->config.intf[intf_num].intf_desc.bLength = intf_desc->bLength; + hport->config.intf[intf_num].intf_desc.bDescriptorType = intf_desc->bDescriptorType; + hport->config.intf[intf_num].intf_desc.bInterfaceNumber = intf_desc->bInterfaceNumber; + hport->config.intf[intf_num].intf_desc.bAlternateSetting = intf_desc->bAlternateSetting; + hport->config.intf[intf_num].intf_desc.bNumEndpoints = intf_desc->bNumEndpoints; + hport->config.intf[intf_num].intf_desc.bInterfaceClass = intf_desc->bInterfaceClass; + hport->config.intf[intf_num].intf_desc.bInterfaceSubClass = intf_desc->bInterfaceSubClass; + hport->config.intf[intf_num].intf_desc.bInterfaceProtocol = intf_desc->bInterfaceProtocol; + hport->config.intf[intf_num].intf_desc.iInterface = intf_desc->iInterface; + ep_num = 0; + while (p[DESC_bLength] && (total_len < desc->wTotalLength) && (ep_num < intf_desc->bNumEndpoints)) { + p += p[DESC_bLength]; + total_len += p[DESC_bLength]; + if (p[DESC_bDescriptorType] == USB_DESCRIPTOR_TYPE_ENDPOINT) { + struct usb_endpoint_descriptor *ep_desc = (struct usb_endpoint_descriptor *)p; +#if 0 + USB_LOG_DBG("Endpoint Descriptor:\r\n"); + USB_LOG_DBG("bLength: 0x%02x \r\n", ep_desc->bLength); + USB_LOG_DBG("bDescriptorType: 0x%02x \r\n", ep_desc->bDescriptorType); + USB_LOG_DBG("bEndpointAddress: 0x%02x \r\n", ep_desc->bEndpointAddress); + USB_LOG_DBG("bmAttributes: 0x%02x \r\n", ep_desc->bmAttributes); + USB_LOG_DBG("wMaxPacketSize: 0x%04x \r\n", ep_desc->wMaxPacketSize); + USB_LOG_DBG("bInterval: 0x%02x \r\n", ep_desc->bInterval); +#endif + memset(&hport->config.intf[intf_num].ep[ep_num], 0, sizeof(struct usbh_endpoint)); + + hport->config.intf[intf_num].ep[ep_num].ep_desc.bLength = ep_desc->bLength; + hport->config.intf[intf_num].ep[ep_num].ep_desc.bDescriptorType = ep_desc->bDescriptorType; + hport->config.intf[intf_num].ep[ep_num].ep_desc.bEndpointAddress = ep_desc->bEndpointAddress; + hport->config.intf[intf_num].ep[ep_num].ep_desc.bmAttributes = ep_desc->bmAttributes; + hport->config.intf[intf_num].ep[ep_num].ep_desc.wMaxPacketSize = ep_desc->wMaxPacketSize; + hport->config.intf[intf_num].ep[ep_num].ep_desc.bInterval = ep_desc->bInterval; + ep_num++; + } + } + intf_num++; + } + } + } + } + return 0; +} +#if 0 +static int parse_string_descriptor(struct usbh_hubport *hport, struct usb_string_descriptor *desc, uint8_t str_idx, uint16_t length) +{ + uint8_t string[64 + 1] = { 0 }; + uint8_t *p = (uint8_t *)desc; + + if (desc->bDescriptorType != USB_DESCRIPTOR_TYPE_STRING) { + USB_LOG_ERR("unexpected descriptor 0x%02x\r\n", desc->bDescriptorType); + return -2; + } else { + p += 2; + for (uint32_t i = 0; i < (desc->bLength - 2) / 2; i++) { + string[i] = *p; + p += 2; + } + USB_LOG_DBG("string:%s\r\n", string); + } + return 0; +} +#endif + +static void usbh_print_hubport_info(struct usbh_hubport *hport) +{ + printf("Device Descriptor:\r\n"); + printf("bLength: 0x%02x \r\n", hport->device_desc.bLength); + printf("bDescriptorType: 0x%02x \r\n", hport->device_desc.bDescriptorType); + printf("bcdUSB: 0x%04x \r\n", hport->device_desc.bcdUSB); + printf("bDeviceClass: 0x%02x \r\n", hport->device_desc.bDeviceClass); + printf("bDeviceSubClass: 0x%02x \r\n", hport->device_desc.bDeviceSubClass); + printf("bDeviceProtocol: 0x%02x \r\n", hport->device_desc.bDeviceProtocol); + printf("bMaxPacketSize0: 0x%02x \r\n", hport->device_desc.bMaxPacketSize0); + printf("idVendor: 0x%04x \r\n", hport->device_desc.idVendor); + printf("idProduct: 0x%04x \r\n", hport->device_desc.idProduct); + printf("bcdDevice: 0x%04x \r\n", hport->device_desc.bcdDevice); + printf("iManufacturer: 0x%02x \r\n", hport->device_desc.iManufacturer); + printf("iProduct: 0x%02x \r\n", hport->device_desc.iProduct); + printf("iSerialNumber: 0x%02x \r\n", hport->device_desc.iSerialNumber); + printf("bNumConfigurations: 0x%02x\r\n", hport->device_desc.bNumConfigurations); + + printf("Config Descriptor:\r\n"); + printf("bLength: 0x%02x \r\n", hport->config.config_desc.bLength); + printf("bDescriptorType: 0x%02x \r\n", hport->config.config_desc.bDescriptorType); + printf("wTotalLength: 0x%04x \r\n", hport->config.config_desc.wTotalLength); + printf("bNumInterfaces: 0x%02x \r\n", hport->config.config_desc.bNumInterfaces); + printf("bConfigurationValue: 0x%02x \r\n", hport->config.config_desc.bConfigurationValue); + printf("iConfiguration: 0x%02x \r\n", hport->config.config_desc.iConfiguration); + printf("bmAttributes: 0x%02x \r\n", hport->config.config_desc.bmAttributes); + printf("bMaxPower: 0x%02x \r\n", hport->config.config_desc.bMaxPower); + + for (uint8_t i = 0; i < hport->config.config_desc.bNumInterfaces; i++) { + printf("Interface Descriptor:\r\n"); + printf("bLength: 0x%02x \r\n", hport->config.intf[i].intf_desc.bLength); + printf("bDescriptorType: 0x%02x \r\n", hport->config.intf[i].intf_desc.bDescriptorType); + printf("bInterfaceNumber: 0x%02x \r\n", hport->config.intf[i].intf_desc.bInterfaceNumber); + printf("bAlternateSetting: 0x%02x \r\n", hport->config.intf[i].intf_desc.bAlternateSetting); + printf("bNumEndpoints: 0x%02x \r\n", hport->config.intf[i].intf_desc.bNumEndpoints); + printf("bInterfaceClass: 0x%02x \r\n", hport->config.intf[i].intf_desc.bInterfaceClass); + printf("bInterfaceSubClass: 0x%02x \r\n", hport->config.intf[i].intf_desc.bInterfaceSubClass); + printf("bInterfaceProtocol: 0x%02x \r\n", hport->config.intf[i].intf_desc.bInterfaceProtocol); + printf("iInterface: 0x%02x \r\n", hport->config.intf[i].intf_desc.iInterface); + + for (uint8_t j = 0; j < hport->config.intf[i].intf_desc.bNumEndpoints; j++) { + printf("Endpoint Descriptor:\r\n"); + printf("bLength: 0x%02x \r\n", hport->config.intf[i].ep[j].ep_desc.bLength); + printf("bDescriptorType: 0x%02x \r\n", hport->config.intf[i].ep[j].ep_desc.bDescriptorType); + printf("bEndpointAddress: 0x%02x \r\n", hport->config.intf[i].ep[j].ep_desc.bEndpointAddress); + printf("bmAttributes: 0x%02x \r\n", hport->config.intf[i].ep[j].ep_desc.bmAttributes); + printf("wMaxPacketSize: 0x%04x \r\n", hport->config.intf[i].ep[j].ep_desc.wMaxPacketSize); + printf("bInterval: 0x%02x \r\n", hport->config.intf[i].ep[j].ep_desc.bInterval); + } + } +} + +static int usbh_enumerate(struct usbh_hubport *hport) +{ + struct usb_interface_descriptor *intf_desc; + struct usb_setup_packet *setup; + uint8_t *ep0_buffer; + uint8_t descsize; + int dev_addr; + uint8_t ep_mps; + int ret; + +#define USB_REQUEST_BUFFER_SIZE 256 + /* Allocate buffer for setup and data buffer */ + setup = usb_iomalloc(sizeof(struct usb_setup_packet)); + if (setup == NULL) { + USB_LOG_ERR("Fail to alloc setup\r\n"); + return -ENOMEM; + } + + ep0_buffer = usb_iomalloc(USB_REQUEST_BUFFER_SIZE); + if (ep0_buffer == NULL) { + USB_LOG_ERR("Fail to alloc ep0_buffer\r\n"); + return -ENOMEM; + } + + /* Pick an appropriate packet size for this device + * + * USB 2.0, Paragraph 5.5.3 "Control Transfer Packet Size Constraints" + * + * "An endpoint for control transfers specifies the maximum data + * payload size that the endpoint can accept from or transmit to + * the bus. The allowable maximum control transfer data payload + * sizes for full-speed devices is 8, 16, 32, or 64 bytes; for + * high-speed devices, it is 64 bytes and for low-speed devices, + * it is 8 bytes. This maximum applies to the data payloads of the + * Data packets following a Setup..." + */ + + if (hport->speed == USB_SPEED_HIGH) { + /* For high-speed, we must use 64 bytes */ + ep_mps = 64; + descsize = USB_SIZEOF_DEVICE_DESC; + } else { + /* Eight will work for both low- and full-speed */ + ep_mps = 8; + descsize = 8; + } + + /* Configure EP0 with the initial maximum packet size */ + usbh_ep0_reconfigure(hport->ep0, 0, ep_mps, hport->speed); + + /* Read the first 8 bytes of the device descriptor */ + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = (uint16_t)((USB_DESCRIPTOR_TYPE_DEVICE << 8) | 0); + setup->wIndex = 0; + setup->wLength = descsize; + + ret = usbh_control_transfer(hport->ep0, setup, ep0_buffer); + if (ret < 0) { + USB_LOG_ERR("Failed to get device descriptor,errorcode:%d\r\n", ret); + goto errout; + } + + parse_device_descriptor(hport, (struct usb_device_descriptor *)ep0_buffer, descsize); + + /* Extract the correct max packetsize from the device descriptor */ + ep_mps = ((struct usb_device_descriptor *)ep0_buffer)->bMaxPacketSize0; + + /* And reconfigure EP0 with the correct maximum packet size */ + usbh_ep0_reconfigure(hport->ep0, 0, ep_mps, hport->speed); + + /* Assign a function address to the device connected to this port */ + dev_addr = usbh_devaddr_create(hport); + if (dev_addr < 0) { + USB_LOG_ERR("Failed to allocate devaddr,errorcode:%d\r\n", ret); + goto errout; + } + + /* Set the USB device address */ + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_SET_ADDRESS; + setup->wValue = dev_addr; + setup->wIndex = 0; + setup->wLength = 0; + + ret = usbh_control_transfer(hport->ep0, setup, NULL); + if (ret < 0) { + USB_LOG_ERR("Failed to set devaddr,errorcode:%d\r\n", ret); + goto errout; + } + + /* wait device address set completely */ + usb_osal_msleep(2); + + /* Assign the function address to the port */ + hport->dev_addr = dev_addr; + + /* And reconfigure EP0 with the correct address */ + usbh_ep0_reconfigure(hport->ep0, dev_addr, ep_mps, hport->speed); + + /* Read the full device descriptor if hport is not in high speed*/ + if (descsize < USB_SIZEOF_DEVICE_DESC) { + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = (uint16_t)((USB_DESCRIPTOR_TYPE_DEVICE << 8) | 0); + setup->wIndex = 0; + setup->wLength = USB_SIZEOF_DEVICE_DESC; + + ret = usbh_control_transfer(hport->ep0, setup, ep0_buffer); + if (ret < 0) { + USB_LOG_ERR("Failed to get full device descriptor,errorcode:%d\r\n", ret); + goto errout; + } + + parse_device_descriptor(hport, (struct usb_device_descriptor *)ep0_buffer, USB_SIZEOF_DEVICE_DESC); + } + + /* Read the first 9 bytes of the config descriptor */ + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = (uint16_t)((USB_DESCRIPTOR_TYPE_CONFIGURATION << 8) | 0); + setup->wIndex = 0; + setup->wLength = USB_SIZEOF_CONFIG_DESC; + + ret = usbh_control_transfer(hport->ep0, setup, ep0_buffer); + if (ret < 0) { + USB_LOG_ERR("Failed to get config descriptor,errorcode:%d\r\n", ret); + goto errout; + } + + parse_config_descriptor(hport, (struct usb_configuration_descriptor *)ep0_buffer, USB_SIZEOF_CONFIG_DESC); + + /* Read the full size of the configuration data */ + uint16_t wTotalLength = ((struct usb_configuration_descriptor *)ep0_buffer)->wTotalLength; + + setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_GET_DESCRIPTOR; + setup->wValue = (uint16_t)((USB_DESCRIPTOR_TYPE_CONFIGURATION << 8) | 0); + setup->wIndex = 0; + setup->wLength = wTotalLength; + + ret = usbh_control_transfer(hport->ep0, setup, ep0_buffer); + if (ret < 0) { + USB_LOG_ERR("Failed to get full config descriptor,errorcode:%d\r\n", ret); + goto errout; + } + + parse_config_descriptor(hport, (struct usb_configuration_descriptor *)ep0_buffer, wTotalLength); + + /* Select device configuration 1 */ + setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_DEVICE; + setup->bRequest = USB_REQUEST_SET_CONFIGURATION; + setup->wValue = 1; + setup->wIndex = 0; + setup->wLength = 0; + + ret = usbh_control_transfer(hport->ep0, setup, NULL); + if (ret < 0) { + USB_LOG_ERR("Failed to set configuration,errorcode:%d\r\n", ret); + goto errout; + } + + /*search supported class driver*/ + for (uint8_t i = 0; i < hport->config.config_desc.bNumInterfaces; i++) { + intf_desc = &hport->config.intf[i].intf_desc; + + struct usbh_class_driver *class_driver = (struct usbh_class_driver *)usbh_find_class_driver(intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol, hport->device_desc.idVendor, hport->device_desc.idProduct); + + if (class_driver == NULL) { + USB_LOG_ERR("do not support Class:0x%02x,Subclass:0x%02x,Protocl:0x%02x\r\n", + intf_desc->bInterfaceClass, + intf_desc->bInterfaceSubClass, + intf_desc->bInterfaceProtocol); + + continue; + } + hport->config.intf[i].class_driver = class_driver; + + if (hport->config.intf[i].class_driver->connect) { + ret = CLASS_CONNECT(hport, i); + if (ret < 0) { + ret = CLASS_DISCONNECT(hport, i); + goto errout; + } + } + } + +errout: + if (ret < 0) { + usbh_hport_deactivate(hport); + } + + if (setup) { + usb_iofree(setup); + } + if (ep0_buffer) { + usb_iofree(ep0_buffer); + } + + return ret; +} + +static int usbh_portchange_wait(struct usbh_hubport **hport) +{ + struct usbh_hubport *connport = NULL; + uint32_t flags; + int ret; + + /* Loop until a change in connection state is detected */ + while (1) { + /* Check for a change in the connection state on any root hub port */ + flags = usb_osal_enter_critical_section(); + for (uint8_t port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + connport = &usbh_core_cfg.rhport[port - 1].hport; + + if (connport->port_change) { + connport->port_change = false; + *hport = connport; + usb_osal_leave_critical_section(flags); + return 0; + } + } + /* Is a device connected to an external hub? */ + if (usbh_core_cfg.active_hport) { + connport = (struct usbh_hubport *)usbh_core_cfg.active_hport; + usbh_core_cfg.active_hport = NULL; + *hport = connport; + usb_osal_leave_critical_section(flags); + return 0; + } + /* No changes on any port. Wait for a connection/disconnection event and check again */ + usbh_core_cfg.pscwait = true; + usb_osal_leave_critical_section(flags); + ret = usb_osal_sem_take(usbh_core_cfg.pscsem); + if (ret < 0) { + return ret; + } + } +} + +static void usbh_portchange_detect_thread(void *argument) +{ + struct usbh_hubport *hport = NULL; + uint32_t flags; + + flags = usb_osal_enter_critical_section(); + usb_hc_init(); + + for (uint8_t port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + usbh_core_cfg.rhport[port - 1].hport.port = port; + usbh_core_cfg.rhport[port - 1].devgen.next = 1; + usbh_hport_activate(&usbh_core_cfg.rhport[port - 1].hport); + } + + usb_osal_leave_critical_section(flags); + + while (1) { + usbh_portchange_wait(&hport); + if (hport->connected) { + /*if roothub port,reset port first*/ + if (ROOTHUB(hport)) { + /* Reset the host port */ + usbh_reset_port(hport->port); + usb_osal_msleep(200); + /* Get the current device speed */ + hport->speed = usbh_get_port_speed(hport->port); + USB_LOG_INFO("Bus %u, Port %u connected, %s\r\n", 1, hport->port, speed_table[hport->speed]); + } else { + USB_LOG_INFO("Bus %u, Port %u connected, %s\r\n", hport->parent->index, hport->port, speed_table[hport->speed]); + } + usb_osal_thread_suspend(g_lpworkq.thread); + usbh_enumerate(hport); + usb_osal_thread_resume(g_lpworkq.thread); + } else { + usbh_hport_deactivate(hport); + for (uint8_t i = 0; i < hport->config.config_desc.bNumInterfaces; i++) { + if (hport->config.intf[i].class_driver && hport->config.intf[i].class_driver->disconnect) { + CLASS_DISCONNECT(hport, i); + } + } + + hport->config.config_desc.bNumInterfaces = 0; + + if (ROOTHUB(hport)) { + USB_LOG_INFO("Bus %u,Port:%u disconnected\r\n", 1, hport->port); + } else { + USB_LOG_INFO("Bus %u,Port:%u disconnected\r\n", hport->parent->index, hport->port); + } + } + } +} + +void usbh_external_hport_connect(struct usbh_hubport *hport) +{ + uint32_t flags; + + usbh_hport_activate(hport); + + flags = usb_osal_enter_critical_section(); + + hport->connected = true; + usbh_core_cfg.active_hport = hport; + + if (usbh_core_cfg.pscwait) { + usbh_core_cfg.pscwait = false; + usb_osal_sem_give(usbh_core_cfg.pscsem); + } + + usb_osal_leave_critical_section(flags); +} + +void usbh_external_hport_disconnect(struct usbh_hubport *hport) +{ + uint32_t flags; + + flags = usb_osal_enter_critical_section(); + hport->connected = false; + usbh_core_cfg.active_hport = hport; + + if (usbh_core_cfg.pscwait) { + usbh_core_cfg.pscwait = false; + usb_osal_sem_give(usbh_core_cfg.pscsem); + } + + usb_osal_leave_critical_section(flags); +} + +void usbh_hport_activate(struct usbh_hubport *hport) +{ + struct usbh_endpoint_cfg ep0_cfg; + uint32_t flags; + + flags = usb_osal_enter_critical_section(); + memset(&ep0_cfg, 0, sizeof(struct usbh_endpoint_cfg)); + + ep0_cfg.ep_addr = 0x00; + ep0_cfg.ep_interval = 0x00; + ep0_cfg.ep_mps = 0x08; + ep0_cfg.ep_type = USB_ENDPOINT_TYPE_CONTROL; + ep0_cfg.hport = hport; + /* Allocate memory for roothub port control endpoint */ + usbh_ep_alloc(&hport->ep0, &ep0_cfg); + + usb_osal_leave_critical_section(flags); +} + +void usbh_hport_deactivate(struct usbh_hubport *hport) +{ + uint32_t flags; + + flags = usb_osal_enter_critical_section(); + /* Don't free the control pipe of root hub ports! */ + if (hport->parent != NULL && hport->ep0 != NULL) { + usb_ep_cancel(hport->ep0); + usbh_ep_free(hport->ep0); + hport->ep0 = NULL; + } + /* Free the device address if one has been assigned */ + usbh_devaddr_destroy(hport, hport->dev_addr); + hport->dev_addr = 0; + + usb_osal_leave_critical_section(flags); +} + +void usbh_event_notify_handler(uint8_t event, uint8_t rhport) +{ + switch (event) { + case USBH_EVENT_ATTACHED: + if (!usbh_core_cfg.rhport[rhport - 1].hport.connected) { + usbh_core_cfg.rhport[rhport - 1].hport.connected = true; + usbh_core_cfg.rhport[rhport - 1].hport.port_change = true; + if (usbh_core_cfg.pscwait) { + usbh_core_cfg.pscwait = false; + usb_osal_sem_give(usbh_core_cfg.pscsem); + } + } + break; + case USBH_EVENT_REMOVED: + if (usbh_core_cfg.rhport[rhport - 1].hport.connected) { + usbh_core_cfg.rhport[rhport - 1].hport.connected = false; + usbh_core_cfg.rhport[rhport - 1].hport.port_change = true; + if (usbh_core_cfg.pscwait) { + usbh_core_cfg.pscwait = false; + usb_osal_sem_give(usbh_core_cfg.pscsem); + } + } + break; + default: + break; + } +} + +int usbh_initialize(void) +{ + usb_osal_thread_t usb_thread; + + memset(&usbh_core_cfg, 0, sizeof(struct usbh_core_priv)); + + usbh_workq_initialize(); + + usbh_core_cfg.pscsem = usb_osal_sem_create(0); + if (usbh_core_cfg.pscsem == NULL) { + return -1; + } + + usb_thread = usb_osal_thread_create("usbh_psc", CONFIG_USBHOST_PSC_STACKSIZE, CONFIG_USBHOST_PSC_PRIO, usbh_portchange_detect_thread, NULL); + if (usb_thread == NULL) { + return -1; + } + + return 0; +} + +int lsusb(int argc, char **argv) +{ + usb_slist_t *hub_list; + uint8_t port; + + if (argc < 2) { + printf("Usage: lsusb [options]...\r\n"); + printf("List USB devices\r\n"); + printf(" -v, --verbose\r\n"); + printf(" Increase verbosity (show descriptors)\r\n"); + printf(" -s [[bus]:[devnum]]\r\n"); + printf(" Show only devices with specified device and/or bus numbers (in decimal)\r\n"); + printf(" -d vendor:[product]\r\n"); + printf(" Show only devices with the specified vendor and product ID numbers (in hexadecimal)\r\n"); + printf(" -t, --tree\r\n"); + printf(" Dump the physical USB device hierachy as a tree\r\n"); + printf(" -V, --version\r\n"); + printf(" Show version of program\r\n"); + printf(" -h, --help\r\n"); + printf(" Show usage and help\r\n"); + return 0; + } + + if (argc > 3) { + return 0; + } + + if (strcmp(argv[1], "-t") == 0) { + for (port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + if (usbh_core_cfg.rhport[port - 1].hport.connected) { + printf("/: Bus %02u,VID:PID 0x%04x:0x%04x\r\n", USBH_ROOT_HUB_INDEX, usbh_core_cfg.rhport[port - 1].hport.device_desc.idVendor, usbh_core_cfg.rhport[port - 1].hport.device_desc.idProduct); + + for (uint8_t i = 0; i < usbh_core_cfg.rhport[port - 1].hport.config.config_desc.bNumInterfaces; i++) { + if (usbh_core_cfg.rhport[port - 1].hport.config.intf[i].class_driver->driver_name) { + printf(" |__Port %u,Port addr:0x%02x,If %u,ClassDriver=%s\r\n", usbh_core_cfg.rhport[port - 1].hport.port, usbh_core_cfg.rhport[port - 1].hport.dev_addr, + i, usbh_core_cfg.rhport[port - 1].hport.config.intf[i].class_driver->driver_name); + } + } + } + } + usb_slist_for_each(hub_list, &hub_class_head) + { + usbh_hub_t *hub_class = usb_slist_entry(hub_list, struct usbh_hub, list); + + for (port = USBH_HUB_PORT_START_INDEX; port <= hub_class->nports; port++) { + if (hub_class->child[port - 1].connected) { + printf("/: Bus %02u,VID:PID 0x%04x:0x%04x\r\n", hub_class->index, hub_class->child[port - 1].device_desc.idVendor, hub_class->child[port - 1].device_desc.idProduct); + + for (uint8_t i = 0; i < hub_class->child[port - 1].config.config_desc.bNumInterfaces; i++) { + if (hub_class->child[port - 1].config.intf[i].class_driver->driver_name) { + printf(" |__Port %u,Port addr:0x%02x,If %u,ClassDriver=%s\r\n", hub_class->child[port - 1].port, hub_class->child[port - 1].dev_addr, + i, hub_class->child[port - 1].config.intf[i].class_driver->driver_name); + } + } + } + } + } + } else if (strcmp(argv[1], "-v") == 0) { + for (port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + if (usbh_core_cfg.rhport[port - 1].hport.connected) { + printf("Bus %02u,Port %u,Port addr:0x%02x,VID:PID 0x%04x:0x%04x\r\n", USBH_ROOT_HUB_INDEX, usbh_core_cfg.rhport[port - 1].hport.port, usbh_core_cfg.rhport[port - 1].hport.dev_addr, + usbh_core_cfg.rhport[port - 1].hport.device_desc.idVendor, usbh_core_cfg.rhport[port - 1].hport.device_desc.idProduct); + usbh_print_hubport_info(&usbh_core_cfg.rhport[port - 1].hport); + } + } + + usb_slist_for_each(hub_list, &hub_class_head) + { + usbh_hub_t *hub_class = usb_slist_entry(hub_list, struct usbh_hub, list); + + for (port = USBH_HUB_PORT_START_INDEX; port <= hub_class->nports; port++) { + if (hub_class->child[port - 1].connected) { + printf("Bus %02u,Port %u,Port addr:0x%02x,VID:PID 0x%04x:0x%04x\r\n", hub_class->index, hub_class->child[port - 1].port, hub_class->child[port - 1].dev_addr, + hub_class->child[port - 1].device_desc.idVendor, hub_class->child[port - 1].device_desc.idProduct); + usbh_print_hubport_info(&hub_class->child[port - 1]); + } + } + } + } + + return 0; +} + +struct usbh_hubport *usbh_get_hubport(uint8_t dev_addr) +{ + usb_slist_t *hub_list; + uint8_t port; + + for (port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + if (usbh_core_cfg.rhport[port - 1].hport.connected) { + if (usbh_core_cfg.rhport[port - 1].hport.dev_addr == dev_addr) { + return &usbh_core_cfg.rhport[port - 1].hport; + } + } + } + usb_slist_for_each(hub_list, &hub_class_head) + { + usbh_hub_t *hub_class = usb_slist_entry(hub_list, struct usbh_hub, list); + + for (port = USBH_HUB_PORT_START_INDEX; port <= hub_class->nports; port++) { + if (hub_class->child[port - 1].connected) { + if (hub_class->child[port - 1].dev_addr == dev_addr) { + return &hub_class->child[port - 1]; + } + } + } + } + return NULL; +} + +void *usbh_get_class(uint8_t dev_addr, uint8_t intf) +{ + usb_slist_t *hub_list; + uint8_t port; + + for (port = USBH_HUB_PORT_START_INDEX; port <= CONFIG_USBHOST_RHPORTS; port++) { + if (usbh_core_cfg.rhport[port - 1].hport.connected) { + if (usbh_core_cfg.rhport[port - 1].hport.dev_addr == dev_addr) { + if (usbh_core_cfg.rhport[port - 1].hport.config.intf[intf].priv) { + return usbh_core_cfg.rhport[port - 1].hport.config.intf[intf].priv; + } + } + } + } + usb_slist_for_each(hub_list, &hub_class_head) + { + usbh_hub_t *hub_class = usb_slist_entry(hub_list, struct usbh_hub, list); + + for (port = USBH_HUB_PORT_START_INDEX; port <= hub_class->nports; port++) { + if (hub_class->child[port - 1].connected) { + if (hub_class->child[port - 1].dev_addr == dev_addr) { + if (hub_class->child[port - 1].config.intf[intf].priv) { + return hub_class->child[port - 1].config.intf[intf].priv; + } + } + } + } + } + return NULL; +} + +const struct usbh_class_info class_info_table[] = { + + { .class = USB_DEVICE_CLASS_CDC, + .subclass = CDC_ABSTRACT_CONTROL_MODEL, + .protocol = CDC_COMMON_PROTOCOL_AT_COMMANDS, + .vid = 0x00, + .pid = 0x00, + .class_driver = &cdc_acm_class_driver }, + { .class = USB_DEVICE_CLASS_HID, + .subclass = HID_SUBCLASS_BOOTIF, + .protocol = HID_PROTOCOL_KEYBOARD, + .vid = 0x00, + .pid = 0x00, + .class_driver = &hid_class_driver }, + { .class = USB_DEVICE_CLASS_HID, + .subclass = HID_SUBCLASS_BOOTIF, + .protocol = HID_PROTOCOL_MOUSE, + .vid = 0x00, + .pid = 0x00, + .class_driver = &hid_class_driver }, + { .class = USB_DEVICE_CLASS_MASS_STORAGE, + .subclass = MSC_SUBCLASS_SCSI, + .protocol = MSC_PROTOCOL_BULK_ONLY, + .vid = 0x00, + .pid = 0x00, + .class_driver = &msc_class_driver }, +#ifdef CONFIG_USBHOST_HUB + { .class = USB_DEVICE_CLASS_HUB, + .subclass = 0, + .protocol = 0, + .vid = 0x00, + .pid = 0x00, + .class_driver = &hub_class_driver }, + { .class = USB_DEVICE_CLASS_HUB, + .subclass = 0, + .protocol = 1, + .vid = 0x00, + .pid = 0x00, + .class_driver = &hub_class_driver }, +#endif +}; + +static const struct usbh_class_driver *usbh_find_class_driver(uint8_t class, uint8_t subcalss, uint8_t protocol, uint16_t vid, uint16_t pid) +{ + for (uint8_t i = 0; i < sizeof(class_info_table) / sizeof(class_info_table[0]); i++) { + if (class == class_info_table[i].class && + subcalss == class_info_table[i].subclass && + protocol == class_info_table[i].protocol) { + /* If this is a vendor-specific class ID, then the VID and PID have to match as well. */ + if (class == USB_DEVICE_CLASS_VEND_SPECIFIC) { + if (vid == class_info_table[i].vid && + pid == class_info_table[i].pid) { + return class_info_table[i].class_driver; + } + } + return class_info_table[i].class_driver; + } + } + + return NULL; +} diff --git a/core/usbh_core.h b/core/usbh_core.h new file mode 100644 index 00000000..ee74e352 --- /dev/null +++ b/core/usbh_core.h @@ -0,0 +1,128 @@ +/** + * @file usbh_core.h + * + * Copyright (c) 2022 sakumisu + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _USBH_CORE_H +#define _USBH_CORE_H + +#include "usb_util.h" +#include "usb_def.h" +#include "usb_hc.h" +#include "usb_osal.h" +#include "usb_workq.h" +#include "usbh_hub.h" +#include "usb_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define USBH_ROOT_HUB_INDEX 1 /* roothub index*/ +#define USBH_EX_HUB_INDEX 2 /* external hub index */ +#define USBH_HUB_PORT_START_INDEX 1 /* first hub port index */ + +#ifdef CONFIG_USBHOST_HUB +#define ROOTHUB(hport) ((hport)->parent == NULL) +#else +#define ROOTHUB(hport) true +#endif + +#define CLASS_CONNECT(hport,i) ((hport)->config.intf[i].class_driver->connect(hport, i)) +#define CLASS_DISCONNECT(hport,i) ((hport)->config.intf[i].class_driver->disconnect(hport, i)) + +enum usbh_event_type { + USBH_EVENT_ATTACHED, + USBH_EVENT_REMOVED, +}; + +struct usbh_class_info { + uint8_t class; /* Base device class code */ + uint8_t subclass; /* Sub-class, depends on base class. Eg. */ + uint8_t protocol; /* Protocol, depends on base class. Eg. */ + uint16_t vid; /* Vendor ID (for vendor/product specific devices) */ + uint16_t pid; /* Product ID (for vendor/product specific devices) */ + const struct usbh_class_driver *class_driver; +}; + +struct usbh_hubport; +struct usbh_class_driver { + const char *driver_name; + int (*connect)(struct usbh_hubport *hport, uint8_t intf); + int (*disconnect)(struct usbh_hubport *hport, uint8_t intf); +}; + +typedef struct usbh_endpoint { + struct usb_endpoint_descriptor ep_desc; +} usbh_endpoint_t; + +typedef struct usbh_interface { + struct usb_interface_descriptor intf_desc; + struct usbh_endpoint ep[CONFIG_USBHOST_EP_NUM]; + struct usbh_class_driver *class_driver; + void *priv; +} usbh_interface_t; + +typedef struct usbh_configuration { + struct usb_configuration_descriptor config_desc; + struct usbh_interface intf[CONFIG_USBHOST_INTF_NUM]; +} usbh_configuration_t; + +typedef struct usbh_hubport { + bool connected; /* True: device connected; false: disconnected */ + bool port_change; /* True: port changed; false: port do not change */ + uint8_t port; /* Hub port index */ + uint8_t dev_addr; /* device address */ + uint8_t speed; /* device speed */ + usbh_epinfo_t ep0; /* control ep info */ + struct usb_device_descriptor device_desc; + struct usbh_configuration config; +#if 0 + uint8_t* config_desc; +#endif + struct usbh_hub *parent; /*if NULL, is roothub*/ +} usbh_hubport_t; + +typedef struct usbh_hub { + usb_slist_t list; + uint8_t index; /* Hub index */ + uint8_t nports; /* Hub port number */ + uint8_t dev_addr; /* Hub device address */ + usbh_epinfo_t intin; + uint8_t *int_buffer; + struct usb_setup_packet *setup; + struct hub_port_status *port_status; + struct usb_hub_descriptor hub_desc; + struct usbh_hubport child[CONFIG_USBHOST_EHPORTS]; + struct usbh_hubport *parent; /* Parent hub port */ + struct usb_work work; +} usbh_hub_t; + +void usbh_event_notify_handler(uint8_t event, uint8_t rhport); + +int usbh_initialize(void); +int lsusb(int argc, char **argv); +struct usbh_hubport *usbh_get_hubport(uint8_t dev_addr); +void *usbh_get_class(uint8_t dev_addr, uint8_t intf); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/usb_config.h b/usb_config.h new file mode 100644 index 00000000..9715686a --- /dev/null +++ b/usb_config.h @@ -0,0 +1,46 @@ +#ifndef _USB_CONFIG_H +#define _USB_CONFIG_H + +/* USB DEVICE Configuration */ + +/* USB HOST Configuration */ +#ifndef CONFIG_USBHOST_RHPORTS +#define CONFIG_USBHOST_RHPORTS 1 +#endif + +#ifndef CONFIG_USBHOST_EHPORTS +#define CONFIG_USBHOST_EHPORTS 4 +#endif + +#ifndef CONFIG_USBHOST_INTF_NUM +#define CONFIG_USBHOST_INTF_NUM 6 +#endif + +#ifndef CONFIG_USBHOST_EP_NUM +#define CONFIG_USBHOST_EP_NUM 2 +#endif + +#ifndef CONFIG_USBHOST_HPWORKQ_PRIO +#define CONFIG_USBHOST_HPWORKQ_PRIO 5 +#endif +#ifndef CONFIG_USBHOST_HPWORKQ_STACKSIZE +#define CONFIG_USBHOST_HPWORKQ_STACKSIZE 2048 +#endif + +#ifndef CONFIG_USBHOST_LPWORKQ_PRIO +#define CONFIG_USBHOST_LPWORKQ_PRIO 1 +#endif +#ifndef CONFIG_USBHOST_LPWORKQ_STACKSIZE +#define CONFIG_USBHOST_LPWORKQ_STACKSIZE 2048 +#endif + +#ifndef CONFIG_USBHOST_PSC_PRIO +#define CONFIG_USBHOST_PSC_PRIO 4 +#endif +#ifndef CONFIG_USBHOST_PSC_STACKSIZE +#define CONFIG_USBHOST_PSC_STACKSIZE 2048 +#endif + +#define CONFIG_USBHOST_ASYNCH + +#endif \ No newline at end of file