diff --git a/_targets/Makefile.armv7a7-imx6ull b/_targets/Makefile.armv7a7-imx6ull index d081a2f62..cfff821b5 100644 --- a/_targets/Makefile.armv7a7-imx6ull +++ b/_targets/Makefile.armv7a7-imx6ull @@ -13,5 +13,5 @@ DEFAULT_COMPONENTS += libusbclient cdc-demo DEFAULT_COMPONENTS += imx6ull-uart imx6ull-otp DEFAULT_COMPONENTS += imx6ull-wdg imx6ull-i2c imx6ull-sdio -DEFAULT_COMPONENTS += libusbehci umass usbacm +DEFAULT_COMPONENTS += libusbehci umass libusbdrv-umass usbacm libusbdrv-usbacm DEFAULT_COMPONENTS += libsensors sensors diff --git a/_targets/Makefile.armv7m7-imxrt106x b/_targets/Makefile.armv7m7-imxrt106x index 064898c2c..3b64b28c3 100644 --- a/_targets/Makefile.armv7m7-imxrt106x +++ b/_targets/Makefile.armv7m7-imxrt106x @@ -9,9 +9,9 @@ DEFAULT_COMPONENTS := imxrt-multi ifneq (, $(findstring 117, $(TARGET))) - DEFAULT_COMPONENTS += libusbclient imxrt-flash cdc-demo imxrt117x-otp libusbehci umass usbacm + DEFAULT_COMPONENTS += libusbclient imxrt-flash cdc-demo imxrt117x-otp libusbehci umass libusbdrv-umass usbacm libusbdrv-usbacm else ifneq (, $(findstring 105, $(TARGET))) # placeholder else ifneq (, $(findstring 106, $(TARGET))) - DEFAULT_COMPONENTS += libusbclient imxrt-flash cdc-demo libimxrt-edma libusbehci umass usbacm + DEFAULT_COMPONENTS += libusbclient imxrt-flash cdc-demo libimxrt-edma libusbehci umass libusbdrv-umass usbacm libusbdrv-usbacm endif diff --git a/_targets/Makefile.ia32-generic b/_targets/Makefile.ia32-generic index b939c8ce9..1c24047e8 100644 --- a/_targets/Makefile.ia32-generic +++ b/_targets/Makefile.ia32-generic @@ -6,4 +6,4 @@ # Copyright 2019 Phoenix Systems # -DEFAULT_COMPONENTS := pc-tty uart16550 pc-ata +DEFAULT_COMPONENTS := pc-tty uart16550 pc-ata libusbehci umass libusbdrv-umass diff --git a/storage/umass/Makefile b/storage/umass/Makefile index 9e8c90608..e0166e4ec 100644 --- a/storage/umass/Makefile +++ b/storage/umass/Makefile @@ -1,11 +1,21 @@ # # Makefile for Phoenix-RTOS USB Mass Storage driver # -# Copyright 2020 Phoenix Systems +# Copyright 2020, 2024 Phoenix Systems # -NAME := umass +ifeq ($(TARGET_FAMILY),ia32) + UMASS_LIBS := libext2 + UMASS_CFLAGS := -DUMASS_MOUNT_EXT2 +endif + +NAME := libusbdrv-umass LOCAL_SRCS := umass.c -LIBS := libusb +LOCAL_CFLAGS += $(UMASS_CFLAGS) +include $(static-lib.mk) +NAME := umass +LOCAL_SRCS := umass.c srv.c +LIBS := libusb $(UMASS_LIBS) +LOCAL_CFLAGS += $(UMASS_CFLAGS) include $(binary.mk) diff --git a/storage/umass/scsi.h b/storage/umass/scsi.h new file mode 100644 index 000000000..99cd28b26 --- /dev/null +++ b/storage/umass/scsi.h @@ -0,0 +1,63 @@ +/* + * Phoenix-RTOS + * + * USB Mass Storage class driver + * + * SCSI transparent command set definitions header + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * %LICENSE% + */ + + +#ifndef _SCSI_H_ +#define _SCSI_H_ + +#include + + +/* Opcodes */ +#define SCSI_REQUEST_SENSE 0x03 +#define SCSI_INQUIRY 0x12 + + +typedef struct { + uint8_t opcode; + uint8_t action_misc0; /* [8:3] action, [3:0] misc0 */ + uint32_t lba; + uint8_t misc1; + uint16_t length; + uint8_t control; +} __attribute__((packed)) scsi_cdb10_t; + + +typedef struct { + uint8_t opcode; + uint8_t misc0[3]; + uint8_t length; + uint8_t control; +} __attribute__((packed)) scsi_cdb6_t; + + +typedef struct { + uint8_t errorcode; + uint8_t segnum; + uint8_t misc0_sensekey; /* [8:4] misc0, [4:0] sensekey */ + uint8_t misc[17]; +} __attribute__((packed)) scsi_sense_t; + + +typedef struct { + uint8_t qualifier_devicetype; /* [8:5] qualifier, [5:0] devicetype */ + uint8_t misc0; + uint8_t version; + uint8_t misc1[5]; + char vendorid[8]; + char productid[16]; + uint8_t misc[4]; +} __attribute__((packed)) scsi_inquiry_t; + + +#endif diff --git a/storage/umass/srv.c b/storage/umass/srv.c new file mode 100644 index 000000000..2c0a11bdf --- /dev/null +++ b/storage/umass/srv.c @@ -0,0 +1,72 @@ +/* + * Phoenix-RTOS + * + * USB Mass Storage class driver + * + * Device driver server + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include "umass.h" +#include + + +static void printHelp(const char *name) +{ + fprintf(stderr, + "Usage: %s [opts]\n" + " -r Mount as rootfs\n" + " -h Print this help\n", + name); +} + + +int main(int argc, char *argv[]) +{ + int ret; + char c; + oid_t oid; + umass_args_t umass_args = { .mount_root = false }; + usb_driver_t *driver = usb_registeredDriverPop(); + + if (driver == NULL) { + fprintf(stderr, "umass: no driver registered!"); + return 1; + } + + if (argc > 1) { + /* Process command line options */ + for (;;) { + c = getopt(argc, argv, "hr"); + if (c == -1) { + break; + } + switch (c) { + case 'r': + umass_args.mount_root = true; + break; + case 'h': + default: + printHelp(argv[0]); + return 0; + } + } + } + + if (!umass_args.mount_root) { + /* Wait for root filesystem if not responsible for mounting it */ + while (lookup("/", NULL, &oid) < 0) { + usleep(10000); + } + } + + ret = usb_driverProcRun(driver, &umass_args); + + return ret == 0 ? 0 : 1; +} diff --git a/storage/umass/umass.c b/storage/umass/umass.c index acb67d102..a8efc4236 100644 --- a/storage/umass/umass.c +++ b/storage/umass/umass.c @@ -3,8 +3,8 @@ * * USB Mass Storage class driver * - * Copyright 2021 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -27,12 +26,33 @@ #include #include +#include + +#include + +#define UMASS_DEBUG 0 + +#ifdef UMASS_MOUNT_EXT2 +#include +#endif + #include #include #include "../pc-ata/mbr.h" +#include "umass.h" +#include "scsi.h" + +#ifndef UMASS_N_MSG_THREADS +#define UMASS_N_MSG_THREADS 2 +#endif -#define UMASS_N_MSG_THREADS 1 +#ifndef UMASS_N_POOL_THREADS +#define UMASS_N_POOL_THREADS 2 +#endif + +#define UMASS_TRANSMIT_RETRIES 3 +#define UMASS_INIT_RETRIES 10 #define UMASS_SECTOR_SIZE 512 @@ -42,6 +62,16 @@ #define CBW_SIG 0x43425355 #define CSW_SIG 0x53425355 +#define USB_SUBCLASS_SCSI 0x06 +#define USB_PROTOCOL_BULK 0x50 + +/* clang-format off */ +#define LOG(str_, ...) do { printf("umass: " str_ "\n", ##__VA_ARGS__); } while (0) +#define LOG_ERROR(str_, ...) LOG("error: " str_, ##__VA_ARGS__) +#define TRACE(str_, ...) do { if (0) LOG("trace (%d): " str_, __LINE__, ##__VA_ARGS__); } while (0) +#define DEBUG(str_, ...) do { if (UMASS_DEBUG) LOG("debug: " str_, ##__VA_ARGS__); } while (0) +/* clang-format on */ + typedef struct { uint32_t sig; @@ -62,176 +92,432 @@ typedef struct { } __attribute__((packed)) umass_csw_t; +/* Device access callbacks */ +static ssize_t umass_read(id_t id, off_t offs, char *buf, size_t len); +static ssize_t umass_write(id_t id, off_t offs, const char *buf, size_t len); + + +/* Thread types */ +static void umass_fsthr(void *arg); +static void umass_poolthr(void *arg); +static void umass_msgthr(void *arg); + + +/* Filesystem callbacks types */ +typedef int (*fs_handler_t)(void *, msg_t *); +typedef int (*fs_unmount_t)(void *); +typedef int (*fs_mount_t)(oid_t *, unsigned int, typeof(umass_read) *, typeof(umass_write) *, void **); + + typedef struct { - uint8_t opcode; - uint8_t action : 5; - uint8_t misc0 : 3; - uint32_t lba; - uint8_t misc1; - uint16_t length; - uint8_t control; -} __attribute__((packed)) scsi_cdb10_t; + rbnode_t node; /* RBTree node */ + char name[16]; /* Filesystem name */ + uint8_t type; /* Compatible partition type */ + fs_handler_t handler; /* Message handler callback */ + fs_unmount_t unmount; /* Unmount callback */ + fs_mount_t mount; /* Mount callback */ +} umass_fs_t; + + +typedef struct _umass_part_t { + /* Partition data */ + unsigned int idx; /* Partition index */ + unsigned int port; /* Partition port */ + uint32_t start; /* Partition start */ + uint32_t sectors; /* Number of sectors */ + + /* Filesystem data */ + umass_fs_t *fs; /* Mounted filesystem */ + void *fdata; /* Mounted filesystem data */ + + /* Partition filesystem thread stack */ + char fsstack[4 * _PAGE_SIZE] __attribute__((aligned(8))); +} umass_part_t; + + +typedef struct _umass_req_t { + msg_t msg; /* Request msg */ + msg_rid_t rid; /* Request receiving context */ + umass_part_t *part; /* Request receiver partition */ + struct _umass_req_t *prev, *next; /* Doubly linked list */ +} umass_req_t; typedef struct umass_dev { + idnode_t node; /* Device ID */ + char buffer[8 * UMASS_SECTOR_SIZE]; - struct umass_dev *prev, *next; usb_devinfo_t instance; char path[32]; int pipeCtrl; int pipeIn; int pipeOut; - int id; int fileId; int tag; unsigned port; - uint32_t partOffset; - uint32_t partSize; handle_t lock; + + umass_part_t part; /* TODO extend for more partitions */ + + usb_driver_t *drv; } umass_dev_t; static struct { - umass_dev_t *devices; - char stack[UMASS_N_MSG_THREADS][1024] __attribute__((aligned(8))); - unsigned drvport; + idtree_t devices; + umass_dev_t device; + unsigned msgport; handle_t lock; - int lastId; + rbtree_t fss; /* Registered filesystems */ + + umass_req_t *rqueue; /* Requests FIFO queue */ + handle_t rlock, rcond; /* Requests synchronization */ + + bool mount_root; + + /* Message threads stacks */ + char mstacks[UMASS_N_MSG_THREADS][2 * _PAGE_SIZE] __attribute__((aligned(8))); + + /* Pool threads stacks */ + char pstacks[UMASS_N_POOL_THREADS][4 * _PAGE_SIZE] __attribute__((aligned(8))); } umass_common; static const usb_device_id_t filters[] = { - /* USB Mass Storage class */ - { USBDRV_ANY, USBDRV_ANY, USB_CLASS_MASS_STORAGE, USBDRV_ANY, USBDRV_ANY }, + { USBDRV_ANY, USBDRV_ANY, USB_CLASS_MASS_STORAGE, USB_SUBCLASS_SCSI, USB_PROTOCOL_BULK }, }; -static int umass_transmit(umass_dev_t *dev, void *cmd, size_t clen, char *data, size_t dlen, int dir) +#ifdef UMASS_MOUNT_EXT2 +static int umass_registerfs(const char *name, uint8_t type, fs_mount_t mount, fs_unmount_t unmount, fs_handler_t handler) +{ + umass_fs_t *fs = malloc(sizeof(umass_fs_t)); + if (fs == NULL) { + return -ENOMEM; + } + + strncpy(fs->name, name, sizeof(fs->name)); + fs->name[sizeof(fs->name) - 1] = '\0'; + fs->type = type; + fs->mount = mount; + fs->unmount = unmount; + fs->handler = handler; + lib_rbInsert(&umass_common.fss, &fs->node); + + return EOK; +} +#endif + + +static int umass_cmpfs(rbnode_t *node1, rbnode_t *node2) +{ + umass_fs_t *fs1 = lib_treeof(umass_fs_t, node, node1); + umass_fs_t *fs2 = lib_treeof(umass_fs_t, node, node2); + + return strcmp(fs1->name, fs2->name); +} + + +static int umass_scsiRequestSense(umass_dev_t *dev, char *odata); + + +static int _umass_transmit(umass_dev_t *dev, void *cmd, size_t clen, char *data, size_t dlen, int dir) { + scsi_sense_t *sense; umass_cbw_t cbw = { 0 }; umass_csw_t csw = { 0 }; - int ret = 0, bytes = 0; + int ret = -1, bytes = 0; int dataPipe; + int i; if (clen > 16) return -1; - dataPipe = (dir == usb_dir_out) ? dev->pipeOut : dev->pipeIn; - cbw.sig = CBW_SIG; - cbw.tag = dev->tag++; - cbw.dlen = dlen; - cbw.flags = (dir == usb_dir_out) ? UMASS_WRITE : UMASS_READ; - cbw.lun = 0; - cbw.clen = clen; - memcpy(cbw.cmd, cmd, clen); - - if ((ret = usb_transferBulk(dev->pipeOut, &cbw, sizeof(cbw), usb_dir_out)) != sizeof(cbw)) { - fprintf(stderr, "umass_transmit: usb_transferBulk OUT failed\n"); - return -EIO; - } + for (i = 0; ret < 0 && i < UMASS_TRANSMIT_RETRIES; i++) { + dataPipe = (dir == usb_dir_out) ? dev->pipeOut : dev->pipeIn; + cbw.sig = CBW_SIG; + cbw.tag = dev->tag++; + cbw.dlen = dlen; + cbw.flags = (dir == usb_dir_out) ? UMASS_WRITE : UMASS_READ; + cbw.lun = 0; + cbw.clen = clen; + memcpy(cbw.cmd, cmd, clen); + + ret = usb_transferBulk(dev->drv, dev->pipeOut, &cbw, sizeof(cbw), usb_dir_out); + if (ret != sizeof(cbw)) { + fprintf(stderr, "umass_transmit: usb_transferBulk OUT failed\n"); + return -EIO; + } + + /* Optional data transfer */ + if (dlen > 0) { + ret = usb_transferBulk(dev->drv, dataPipe, data, dlen, dir); + if (ret < 0) { + fprintf(stderr, "umass_transmit: umass_transmit data transfer failed\n"); + return ret; + } + bytes = ret; + } + + ret = usb_transferBulk(dev->drv, dev->pipeIn, &csw, sizeof(csw), usb_dir_in); + if (ret != sizeof(csw)) { + fprintf(stderr, "umass_transmit: usb_transferBulk IN transfer failed\n"); + return -EIO; + } - /* Optional data transfer */ - if (dlen > 0) { - if ((ret = usb_transferBulk(dataPipe, data, dlen, dir)) < 0) { - fprintf(stderr, "umass_transmit: umass_transmit data transfer failed\n"); - return ret; + /* Transfer finished, check transfer correctness */ + if (csw.sig != CSW_SIG || csw.tag != cbw.tag || csw.status != 0) { + if (csw.status == 1) { + ret = umass_scsiRequestSense(dev, dev->buffer); + if (ret < 0) { + DEBUG("REQUEST SENSE failed: %d", ret); + } + else { + sense = (scsi_sense_t *)dev->buffer; + DEBUG("REQUEST SENSE code=0x%x, sense key code=0x%x", sense->errorcode, (sense->misc0_sensekey & 0xf)); + } + + /* Retry transfer */ + ret = -1; + bytes = 0; + continue; + } + else { + DEBUG("transfer incorrect.\n csw.sig=0x%x, csw.tag=0x%x, cbw.tag=0x%x, csw.status=%d\n", csw.sig, csw.tag, + cbw.tag, csw.status); + return -EIO; + } } - bytes = ret; } - if ((ret = usb_transferBulk(dev->pipeIn, &csw, sizeof(csw), usb_dir_in)) != sizeof(csw)) { - fprintf(stderr, "umass_transmit: usb_transferBulk IN transfer failed\n"); + return bytes; +} + + +/* Left commented out: can be useful when handling devices of other types than direct-access + * (non-zero "Peripheral Device Type" in INQUIRY) */ +#if 0 +static int umass_getMaxLUN(umass_dev_t *dev, int ifaceNum, int *maxlun) +{ + usb_setup_packet_t setup = (usb_setup_packet_t) { + .bmRequestType = REQUEST_DIR_DEV2HOST | REQUEST_TYPE_CLASS | REQUEST_RECIPIENT_INTERFACE, + .bRequest = 0xFE, /* Get Max LUN */ + .wValue = 0, + .wIndex = ifaceNum, + .wLength = 1, + }; + int ret; + + ret = usb_transferControl(dev->drv, dev->pipeCtrl, &setup, maxlun, 1, usb_dir_in); + if (ret < 0) { + LOG("get max LUN failed"); + dev->maxlun = 0; return -EIO; } - /* Transferred finished, check transfer correctness */ - if (csw.sig != CSW_SIG || csw.tag != cbw.tag || csw.status != 0) { - fprintf(stderr, "umass_transmit: transfer incorrect\n"); - return -EIO; + return 0; +} +#endif + + +static int umass_scsiTest(umass_dev_t *dev) +{ + char testCmd[6] = { 0 }; + int ret; + + ret = _umass_transmit(dev, testCmd, sizeof(testCmd), NULL, 0, usb_dir_in); + + return ret == 0 ? 0 : -EIO; +} + + +static int umass_scsiRequestSense(umass_dev_t *dev, char *odata) +{ + uint8_t len = sizeof(scsi_sense_t); + int ret; + scsi_cdb6_t requestSenseCmd = { + .opcode = SCSI_REQUEST_SENSE, + .length = htons(len), + }; + + ret = _umass_transmit(dev, &requestSenseCmd, sizeof(requestSenseCmd), (char *)odata, len, usb_dir_in); + + return ret == sizeof(scsi_sense_t) ? 0 : -EIO; +} + + +static int umass_scsiInquiry(umass_dev_t *dev, char *odata) +{ + uint8_t len = sizeof(scsi_inquiry_t); + int ret; + scsi_cdb6_t inquiryCmd = { + .opcode = SCSI_INQUIRY, + .length = htons(len), + }; + + ret = _umass_transmit(dev, &inquiryCmd, sizeof(inquiryCmd), odata, len, usb_dir_in); + + return ret == sizeof(scsi_inquiry_t) ? 0 : -EIO; +} + + +static int _umass_scsiInit(umass_dev_t *dev) +{ + int ret; + int i, ok; + scsi_inquiry_t *inquiry; + + for (i = 0, ok = 0; ok < 2 && i < UMASS_INIT_RETRIES; i++) { + ok = 0; + + ret = umass_scsiTest(dev); + if (ret == 0) { + DEBUG("TEST UNIT READY succeeded"); + ok++; + } + + /* Some real world are said to not work without doing INQUIRY first */ + ret = umass_scsiInquiry(dev, dev->buffer); + if (ret == 0) { + inquiry = (scsi_inquiry_t *)dev->buffer; + + DEBUG("INQUIRY succeeded\n" + " device type=%x vendorid=%s\n" + " productid=%s", + (inquiry->qualifier_devicetype & 0x1f), inquiry->vendorid, inquiry->productid); + + ok++; + } + else { + DEBUG("INQUIRY failed, ret=%d", ret); + } } - return bytes; + return ok == 2 ? 0 : -1; } -static int umass_check(umass_dev_t *dev) +static int _umass_check(umass_dev_t *dev) { scsi_cdb10_t readcmd = { .opcode = 0x28, .length = htons(0x1) }; - char testcmd[6] = { 0 }; mbr_t *mbr; - - if (umass_transmit(dev, testcmd, sizeof(testcmd), NULL, 0, usb_dir_in) < 0) { - fprintf(stderr, "umass_transmit failed\n"); - return -1; - } + int ret; /* Read MBR */ - if (umass_transmit(dev, &readcmd, sizeof(readcmd), dev->buffer, UMASS_SECTOR_SIZE, usb_dir_in) < 0) { - fprintf(stderr, "umass_transmit 2 failed\n"); + ret = _umass_transmit(dev, &readcmd, sizeof(readcmd), dev->buffer, UMASS_SECTOR_SIZE, usb_dir_in); + if (ret < 0) { + LOG_ERROR("reading MBR failed"); return -1; } mbr = (mbr_t *)dev->buffer; - if (mbr->magic != MBR_MAGIC) + if (mbr->magic != MBR_MAGIC) { + LOG_ERROR("bad MBR magic: 0x%x", mbr->magic); return -1; + } /* Read only the first partition */ - dev->partOffset = mbr->pent[0].start; - dev->partSize = mbr->pent[0].sectors; + dev->part.start = mbr->pent[0].start; + dev->part.sectors = mbr->pent[0].sectors; + dev->part.fs = NULL; + dev->part.fdata = NULL; + dev->part.idx = 0; + + DEBUG("part.start=0x%x, part.sectors=0x%x", dev->part.start, dev->part.sectors); return 0; } -static int umass_read(umass_dev_t *dev, off_t offs, char *buf, size_t len) +static umass_fs_t *umass_getfs(const char *name) +{ + umass_fs_t fs; + + strncpy(fs.name, name, sizeof(fs.name)); + fs.name[sizeof(fs.name) - 1] = '\0'; + + return lib_treeof(umass_fs_t, node, lib_rbFind(&umass_common.fss, &fs.node)); +} + + +/* TODO: handle mounting other filesystems than ext2 */ +#if 0 +static umass_fs_t *umass_findfs(uint8_t type) +{ + rbnode_t *node; + umass_fs_t *fs; + + for (node = lib_rbMinimum(umass_common.fss.root); node != NULL; node = lib_rbNext(node)) { + fs = lib_treeof(umass_fs_t, node, node); + + if (fs->type == type) { + return fs; + } + } + + return NULL; +} +#endif + + +static int umass_readFromDev(umass_dev_t *dev, off_t offs, char *buf, size_t len) { scsi_cdb10_t readcmd = { .opcode = 0x28 }; int ret; - if ((offs % UMASS_SECTOR_SIZE) || (len % UMASS_SECTOR_SIZE)) + if ((offs % UMASS_SECTOR_SIZE) || (len % UMASS_SECTOR_SIZE)) { return -EINVAL; + } - if (offs + len > dev->partSize * UMASS_SECTOR_SIZE) + if (offs + len > dev->part.sectors * UMASS_SECTOR_SIZE) { return -EINVAL; + } len = min(len, sizeof(dev->buffer)); - readcmd.lba = htonl(offs / UMASS_SECTOR_SIZE + dev->partOffset); + readcmd.lba = htonl(offs / UMASS_SECTOR_SIZE + dev->part.start); readcmd.length = htons((uint16_t)(len / UMASS_SECTOR_SIZE)); mutexLock(dev->lock); - if ((ret = umass_transmit(dev, &readcmd, sizeof(readcmd), dev->buffer, len, usb_dir_in)) > 0) - memcpy(buf, dev->buffer, ret); + ret = _umass_transmit(dev, &readcmd, sizeof(readcmd), buf, len, usb_dir_in); mutexUnlock(dev->lock); + if (ret <= 0 && len > 0) { + printf("read transmit failed for offs: %lld\n", offs); + } + return ret; } -static int umass_write(umass_dev_t *dev, off_t offs, const char *buf, size_t len) +static int umass_writeToDev(umass_dev_t *dev, off_t offs, const char *buf, size_t len) { scsi_cdb10_t writecmd = { .opcode = 0x2a }; int ret; - if ((offs % UMASS_SECTOR_SIZE) || (len % UMASS_SECTOR_SIZE)) + if ((offs % UMASS_SECTOR_SIZE) || (len % UMASS_SECTOR_SIZE)) { return -EINVAL; + } - if (offs + len > dev->partSize * UMASS_SECTOR_SIZE) + if (offs + len > dev->part.sectors * UMASS_SECTOR_SIZE) { return -EINVAL; + } len = min(len, sizeof(dev->buffer)); - writecmd.lba = htonl(offs / UMASS_SECTOR_SIZE + dev->partOffset); + writecmd.lba = htonl(offs / UMASS_SECTOR_SIZE + dev->part.start); writecmd.length = htons((uint16_t)(len / UMASS_SECTOR_SIZE)); mutexLock(dev->lock); - memcpy(dev->buffer, buf, len); - ret = umass_transmit(dev, &writecmd, sizeof(writecmd), dev->buffer, len, usb_dir_out); + ret = _umass_transmit(dev, &writecmd, sizeof(writecmd), (char *)buf, len, usb_dir_out); mutexUnlock(dev->lock); + if (ret < 0) { + fprintf(stderr, "write transmit failed for offs: %lld\n", offs); + } return ret; } @@ -242,25 +528,172 @@ static int umass_getattr(umass_dev_t *dev, int type, long long int *attr) if (type != atSize) return -EINVAL; - *attr = dev->partSize * UMASS_SECTOR_SIZE; + *attr = dev->part.sectors * UMASS_SECTOR_SIZE; return EOK; } -static umass_dev_t *umass_devFind(int id) +static umass_dev_t *_umass_devFind(id_t id) +{ + return lib_treeof(umass_dev_t, node, idtree_find(&umass_common.devices, id)); +} + + +static ssize_t umass_read(id_t id, off_t offs, char *buf, size_t len) { - umass_dev_t *tmp = umass_common.devices; + mutexLock(umass_common.lock); + umass_dev_t *dev = _umass_devFind(id); + mutexUnlock(umass_common.lock); + if (dev == NULL) { + return -ENODEV; + } + return umass_readFromDev(dev, offs, buf, len); +} - if (tmp != NULL) { - do { - if (tmp->fileId == id) - return tmp; - tmp = tmp->next; - } while (tmp != umass_common.devices); + +static ssize_t umass_write(id_t id, off_t offs, const char *buf, size_t len) +{ + mutexLock(umass_common.lock); + umass_dev_t *dev = _umass_devFind(id); + mutexUnlock(umass_common.lock); + if (dev == NULL) { + return -ENODEV; } + return umass_writeToDev(dev, offs, buf, len); +} - return NULL; + +static int umass_mountFromDev(umass_dev_t *dev, const char *name, oid_t *oid) +{ + umass_fs_t *fs; + int err; + + if (dev == NULL) { + return -ENODEV; + } + + if (dev->part.fs != NULL) { + return -EEXIST; + } + + fs = umass_getfs(name); + if (fs == NULL) { + return -ENOENT; + } + dev->part.fs = fs; + + err = portCreate(&dev->part.port); + if (err != 0) { + fprintf(stderr, "umass: Can't create partition port!\n"); + return 1; + } + + oid->port = dev->part.port; + oid->id = dev->fileId; + + err = fs->mount(oid, UMASS_SECTOR_SIZE, umass_read, umass_write, &dev->part.fdata); + if (err < 0) { + portDestroy(dev->part.port); + return err; + } + oid->id = err; + + err = beginthread(umass_fsthr, 4, dev->part.fsstack, sizeof(dev->part.fsstack), &dev->part); + if (err < 0) { + dev->part.fs->unmount(dev->part.fdata); + dev->part.fs = NULL; + dev->part.fdata = NULL; + portDestroy(dev->part.port); + return err; + } + + return EOK; +} + + +static int umass_mount(id_t id, const char *name, oid_t *oid) +{ + umass_dev_t *dev; + + mutexLock(umass_common.lock); + dev = _umass_devFind(id); + mutexUnlock(umass_common.lock); + + return umass_mountFromDev(dev, name, oid); +} + + +static void umass_fsthr(void *arg) +{ + umass_part_t *part = (umass_part_t *)arg; + umass_req_t *req; + int umount = 0, ret; + + for (;;) { + /* TODO: use static requests buffer? */ + req = malloc(sizeof(umass_req_t)); + if (req == NULL) { + continue; + } + + req->part = part; + + ret = -1; + while (ret < 0) { + ret = msgRecv(req->part->port, &req->msg, &req->rid); + } + + if (req->msg.type == mtUmount) { + umount = 1; + } + + mutexLock(umass_common.rlock); + + LIST_ADD(&umass_common.rqueue, req); + + condSignal(umass_common.rcond); + mutexUnlock(umass_common.rlock); + + if (umount != 0) { + endthread(); + } + } +} + + +static void umass_poolthr(void *arg) +{ + umass_req_t *req; + + for (;;) { + mutexLock(umass_common.rlock); + + while (umass_common.rqueue == NULL) { + condWait(umass_common.rcond, umass_common.rlock, 0); + } + + /* + * FIFO - assumes LIST_ADD/REMOVE set the queue pointer to the + * element added first + */ + req = umass_common.rqueue; + LIST_REMOVE(&umass_common.rqueue, req); + + mutexUnlock(umass_common.rlock); + + if (req->msg.type == mtUmount) { + req->part->fs->unmount(req->part->fdata); + req->part->fs = NULL; + req->part->fdata = NULL; + } + else { + req->part->fs->handler(req->part->fdata, &req->msg); + } + + msgRespond(req->part->port, &req->msg, req->rid); + free(req); + } } @@ -269,6 +702,8 @@ static void umass_msgthr(void *arg) umass_dev_t *dev; msg_rid_t rid; msg_t msg; + mount_i_msg_t *imnt; + mount_o_msg_t *omnt; for (;;) { if (msgRecv(umass_common.msgport, &msg, &rid) < 0) @@ -282,7 +717,7 @@ static void umass_msgthr(void *arg) } mutexLock(umass_common.lock); - dev = umass_devFind(msg.oid.id); + dev = _umass_devFind(msg.oid.id); mutexUnlock(umass_common.lock); if (dev == NULL) { @@ -297,12 +732,18 @@ static void umass_msgthr(void *arg) msg.o.err = EOK; break; + case mtMount: + imnt = (mount_i_msg_t *)msg.i.raw; + omnt = (mount_o_msg_t *)msg.o.raw; + msg.o.err = umass_mount(msg.oid.id, imnt->fstype, &omnt->oid); + break; + case mtRead: - msg.o.err = umass_read(dev, msg.i.io.offs, msg.o.data, msg.o.size); + msg.o.err = umass_readFromDev(dev, msg.i.io.offs, msg.o.data, msg.o.size); break; case mtWrite: - msg.o.err = umass_write(dev, msg.i.io.offs, msg.i.data, msg.i.size); + msg.o.err = umass_writeToDev(dev, msg.i.io.offs, msg.i.data, msg.i.size); break; case mtGetAttr: @@ -321,176 +762,294 @@ static void umass_msgthr(void *arg) } -static umass_dev_t *umass_devAlloc(void) +static void _umass_devFree(umass_dev_t *dev) +{ + idtree_remove(&umass_common.devices, &dev->node); + resourceDestroy(dev->lock); + free(dev); +} + + +static umass_dev_t *_umass_devAlloc(void) { umass_dev_t *dev; + int rv; - if ((dev = malloc(sizeof(umass_dev_t))) == NULL) { + dev = malloc(sizeof(umass_dev_t)); + if (dev == NULL) { fprintf(stderr, "umass: Not enough memory\n"); return NULL; } - /* Get next device number */ - if (umass_common.devices == NULL) - dev->id = 0; - else - dev->id = umass_common.devices->prev->id + 1; - - dev->fileId = umass_common.lastId++; - - if (mutexCreate(&dev->lock)) { + rv = mutexCreate(&dev->lock); + if (rv < 0) { free(dev); return NULL; } - snprintf(dev->path, sizeof(dev->path), "/dev/umass%d", dev->id); + idtree_alloc(&umass_common.devices, &dev->node); + dev->fileId = idtree_id(&dev->node); + + rv = snprintf(dev->path, sizeof(dev->path), "/dev/umass%d", dev->fileId); + if (rv < 0 || rv >= sizeof(dev->path)) { + _umass_devFree(dev); + return NULL; + } return dev; } -static int umass_handleInsertion(usb_devinfo_t *insertion) +static int umass_mountRoot(umass_dev_t *dev) { - umass_dev_t *dev; - oid_t oid; +#ifdef UMASS_MOUNT_EXT2 + int err; + oid_t roid; - if ((dev = umass_devAlloc()) == NULL) { - fprintf(stderr, "umass: devAlloc failed\n"); - return -ENOMEM; + err = umass_mountFromDev(dev, LIBEXT2_NAME, &roid); + if (err < 0) { + return err; } - dev->instance = *insertion; - if ((dev->pipeCtrl = usb_open(insertion, usb_transfer_control, 0)) < 0) { - free(dev); - fprintf(stderr, "umass: usb_open failed\n"); - return -EINVAL; + err = portRegister(roid.port, "/", &roid); + if (err < 0) { + return err; } - if (usb_setConfiguration(dev->pipeCtrl, 1) != 0) { - free(dev); - fprintf(stderr, "umass: setConfiguration failed\n"); - return -EINVAL; - } + return EOK; +#else + return -ENOSYS; +#endif +} - if ((dev->pipeIn = usb_open(insertion, usb_transfer_bulk, usb_dir_in)) < 0) { - fprintf(stderr, "umass: pipe open failed \n"); - free(dev); - return -EINVAL; - } - if ((dev->pipeOut = usb_open(insertion, usb_transfer_bulk, usb_dir_out)) < 0) { - fprintf(stderr, "umass: pipe open failed\n"); - free(dev); - return -EINVAL; - } - dev->tag = 0; +static int umass_handleInsertion(usb_driver_t *drv, usb_devinfo_t *insertion, usb_event_insertion_t *event) +{ + int err; + umass_dev_t *dev; + oid_t oid; - if (umass_check(dev)) { - fprintf(stderr, "umass: umass_check failed\n"); - free(dev); - return -EINVAL; - } + mutexLock(umass_common.lock); - oid.port = umass_common.msgport; - oid.id = dev->fileId; - if (create_dev(&oid, dev->path) != 0) { - free(dev); - fprintf(stderr, "usb: Can't create dev!\n"); - return -EINVAL; - } + do { + dev = _umass_devAlloc(); + if (dev == NULL) { + fprintf(stderr, "umass: devAlloc failed\n"); + err = -ENOMEM; + break; + } - LIST_ADD(&umass_common.devices, dev); - fprintf(stderr, "umass: New USB Mass Storage device: %s sectors: %d\n", dev->path, dev->partSize); + dev->drv = drv; + dev->instance = *insertion; + dev->pipeCtrl = usb_open(drv, insertion, usb_transfer_control, 0); + if (dev->pipeCtrl < 0) { + fprintf(stderr, "umass: usb_open failed\n"); + _umass_devFree(dev); + err = -EINVAL; + break; + } - return 0; + err = usb_setConfiguration(drv, dev->pipeCtrl, 1); + if (err != 0) { + fprintf(stderr, "umass: setConfiguration failed\n"); + _umass_devFree(dev); + break; + } + + dev->pipeIn = usb_open(drv, insertion, usb_transfer_bulk, usb_dir_in); + if (dev->pipeIn < 0) { + fprintf(stderr, "umass: pipe open failed \n"); + _umass_devFree(dev); + err = -EINVAL; + break; + } + + dev->pipeOut = usb_open(drv, insertion, usb_transfer_bulk, usb_dir_out); + if (dev->pipeOut < 0) { + fprintf(stderr, "umass: pipe open failed\n"); + _umass_devFree(dev); + err = -EINVAL; + break; + } + dev->tag = 0; + + err = _umass_scsiInit(dev); + if (err < 0) { + fprintf(stderr, "umass: device didn't initialize properly after scsi init sequence\n"); + _umass_devFree(dev); + break; + } + + err = _umass_check(dev); + if (err < 0) { + fprintf(stderr, "umass: umass_check failed\n"); + _umass_devFree(dev); + break; + } + + oid.port = umass_common.msgport; + oid.id = dev->fileId; + err = create_dev(&oid, dev->path); + if (err != 0) { + fprintf(stderr, "usb: Can't create dev!\n"); + _umass_devFree(dev); + break; + } + + printf("umass: New USB Mass Storage device: %s sectors: %d\n", dev->path, dev->part.sectors); + + event->deviceCreated = true; + event->dev = oid; + strncpy(event->devPath, dev->path, sizeof(event->devPath)); + event->devPath[sizeof(event->devPath) - 1] = '\0'; + } while (0); + + mutexUnlock(umass_common.lock); + + if (err == 0 && umass_common.mount_root) { + err = umass_mountRoot(dev); + if (err < 0) { + fprintf(stderr, "umass: failed to mount root partition\n"); + return err; + } + umass_common.mount_root = false; /* don't try to mount root again */ + } + + return err; } -static int umass_handleDeletion(usb_deletion_t *del) +static int umass_handleDeletion(usb_driver_t *drv, usb_deletion_t *del) { - umass_dev_t *next, *dev = umass_common.devices; - int cont = 1; + umass_dev_t *dev; + rbnode_t *node, *next; - if (dev == NULL) - return 0; + mutexLock(umass_common.lock); - do { - next = dev->next; + node = lib_rbMinimum(umass_common.devices.root); + while (node != NULL) { + next = lib_rbNext(node); + + dev = lib_treeof(umass_dev_t, node, lib_treeof(idnode_t, linkage, node)); if (dev->instance.bus == del->bus && dev->instance.dev == del->dev && dev->instance.interface == del->interface) { - if (dev == next) - cont = 0; - resourceDestroy(dev->lock); remove(dev->path); - LIST_REMOVE(&umass_common.devices, dev); fprintf(stderr, "umass: Device removed: %s\n", dev->path); - free(dev); - if (!cont) - break; + _umass_devFree(dev); } - dev = next; - } while (dev != umass_common.devices); + + node = next; + } + + mutexUnlock(umass_common.lock); return 0; } -int main(int argc, char *argv[]) +static int umass_handleCompletion(usb_driver_t *drv, usb_completion_t *c, const char *data, size_t len) { + return EOK; +} + + +static int umass_init(usb_driver_t *drv, void *args) +{ + umass_args_t *umass_args = (umass_args_t *)args; int ret, i; - msg_t msg; - usb_msg_t *umsg = (usb_msg_t *)msg.i.raw; - /* Port for communication with the USB stack */ - if (portCreate(&umass_common.drvport) != 0) { - fprintf(stderr, "umass: Can't create port!\n"); - return 1; + if (umass_args != NULL) { + umass_common.mount_root = umass_args->mount_root; } - - /* Port for communication with driver clients */ - if (portCreate(&umass_common.msgport) != 0) { - fprintf(stderr, "umass: Can't create port!\n"); - return 1; + else { + umass_common.mount_root = true; } - if ((usb_connect(filters, sizeof(filters) / sizeof(filters[0]), umass_common.drvport)) < 0) { - fprintf(stderr, "umass: Fail to connect to usb host!\n"); - return 1; - } + do { + ret = mutexCreate(&umass_common.rlock); + if (ret < 0) { + fprintf(stderr, "umass: failed to create server requests mutex\n"); + break; + } - if (mutexCreate(&umass_common.lock)) { - fprintf(stderr, "umass: Can't create mutex!\n"); - return 1; - } + ret = condCreate(&umass_common.rcond); + if (ret < 0) { + fprintf(stderr, "umass: failed to create server requests condition variable\n"); + break; + } - umass_common.lastId = 1; + umass_common.rqueue = NULL; + idtree_init(&umass_common.devices); + lib_rbInit(&umass_common.fss, umass_cmpfs, NULL); - for (i = 0; i < UMASS_N_MSG_THREADS; i++) { - if ((ret = beginthread(umass_msgthr, 4, umass_common.stack[i], sizeof(umass_common.stack[i]), NULL)) != 0) { - fprintf(stderr, "umass: fail to beginthread ret: %d\n", ret); - return 1; +#ifdef UMASS_MOUNT_EXT2 + /* Register filesystems */ + ret = umass_registerfs(LIBEXT2_NAME, LIBEXT2_TYPE, LIBEXT2_MOUNT, LIBEXT2_UNMOUNT, LIBEXT2_HANDLER); + if (ret < 0) { + fprintf(stderr, "umass: failed to register ext2 filesystem\n"); + break; } - } +#endif - for (;;) { - ret = usb_eventsWait(umass_common.drvport, &msg); - if (ret != 0) - return 1; - mutexLock(umass_common.lock); - switch (umsg->type) { - case usb_msg_insertion: - if (umass_handleInsertion(&umsg->insertion) != 0) - fprintf(stderr, "umass: Failed to initialize device!\n"); - break; - case usb_msg_deletion: - umass_handleDeletion(&umsg->deletion); + /* Port for communication with driver clients */ + ret = portCreate(&umass_common.msgport); + if (ret != 0) { + fprintf(stderr, "umass: Can't create message port!\n"); + break; + } + + ret = mutexCreate(&umass_common.lock); + if (ret < 0) { + fprintf(stderr, "umass: Can't create mutex!\n"); + break; + } + + /* Run message threads */ + for (i = 0; i < sizeof(umass_common.mstacks) / sizeof(umass_common.mstacks[0]); i++) { + ret = beginthread(umass_msgthr, 4, umass_common.mstacks[i], sizeof(umass_common.mstacks[i]), NULL); + if (ret != 0) { + fprintf(stderr, "umass: fail to beginthread ret: %d\n", ret); break; - default: - fprintf(stderr, "umass: Error when receiving event from host\n"); + } + } + + /* Run pool threads */ + for (i = 0; i < sizeof(umass_common.pstacks) / sizeof(umass_common.pstacks[0]); i++) { + ret = beginthread(umass_poolthr, 4, umass_common.pstacks[i], sizeof(umass_common.pstacks[i]), NULL); + if (ret < 0) { + fprintf(stderr, "umass: failed to start pool thread %d\n", i); break; + } } - mutexUnlock(umass_common.lock); - } - return 0; + ret = EOK; + } while (0); + + return ret; +} + + +static int umass_destroy(usb_driver_t *drv) +{ + return EOK; +} + + +static usb_driver_t umass_driver = { + .name = "umass", + .handlers = { + .insertion = umass_handleInsertion, + .deletion = umass_handleDeletion, + .completion = umass_handleCompletion, + }, + .ops = { .init = umass_init, .destroy = umass_destroy }, + .filters = filters, + .nfilters = sizeof(filters) / sizeof(filters[0]), + .priv = (void *)&umass_common, +}; + + +__attribute__((constructor)) static void umass_register(void) +{ + usb_driverRegister(&umass_driver); } diff --git a/storage/umass/umass.h b/storage/umass/umass.h new file mode 100644 index 000000000..14e953681 --- /dev/null +++ b/storage/umass/umass.h @@ -0,0 +1,30 @@ +/* + * Phoenix-RTOS + * + * USB Mass Storage class driver + * + * Common header + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _UMASS_H_ +#define _UMASS_H_ + +#include +#include + +#include + + +typedef struct { + bool mount_root; +} umass_args_t; + + +#endif diff --git a/tty/usbacm/Makefile b/tty/usbacm/Makefile index ed8118d52..21f9efd4e 100644 --- a/tty/usbacm/Makefile +++ b/tty/usbacm/Makefile @@ -1,11 +1,14 @@ # # Makefile for Phoenix-RTOS USB CDC ACM driver # -# Copyright 2021 Phoenix Systems +# Copyright 2021, 2024 Phoenix Systems # -NAME := usbacm +NAME := libusbdrv-usbacm LOCAL_SRCS := usbacm.c -LIBS := libusb +include $(static-lib.mk) +NAME := usbacm +LOCAL_SRCS := usbacm.c srv.c +LIBS := libusb include $(binary.mk) diff --git a/tty/usbacm/srv.c b/tty/usbacm/srv.c new file mode 100644 index 000000000..db707ac94 --- /dev/null +++ b/tty/usbacm/srv.c @@ -0,0 +1,34 @@ +/* + * Phoenix-RTOS + * + * USB CDC ACM driver + * + * Device driver server + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * %LICENSE% + */ + + +#include +#include + + +int main(int argc, char *argv[]) +{ + int ret; + usb_driver_t *driver = usb_registeredDriverPop(); + if (driver == NULL) { + fprintf(stderr, "usbacm: no driver registered!"); + return 1; + } + + ret = usb_driverProcRun(driver, NULL); + if (ret < 0) { + fprintf(stderr, "usbacm: failed to start server: %d\n", ret); + } + + return ret == 0 ? 0 : 1; +} diff --git a/tty/usbacm/usbacm.c b/tty/usbacm/usbacm.c index 5b315c00e..7bccb52d9 100644 --- a/tty/usbacm/usbacm.c +++ b/tty/usbacm/usbacm.c @@ -3,8 +3,8 @@ * * USB CDC ACM driver * - * Copyright 2021, 2022 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2022, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * %LICENSE% */ @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -36,12 +37,8 @@ #define USBACM_N_MSG_THREADS 2 #endif -#ifndef USBACM_N_UMSG_THREADS -#define USBACM_N_UMSG_THREADS 2 -#endif - #ifndef RX_FIFO_SIZE -#define RX_FIFO_SIZE 8192 +#define RX_FIFO_SIZE 8192 #endif #ifndef USBACM_N_URBS @@ -56,6 +53,10 @@ #define USBACM_UMSG_PRIO 3 #endif +#ifndef USBACM_SET_LINE_DEFAULT_RATE +#define USBACM_SET_LINE_DEFAULT_RATE 57600 +#endif + #define USBACM_BULK_SZ 2048 /* clang-format off */ @@ -65,7 +66,8 @@ enum { RxStopped = 0, RxRunning = 1, RxDisconnected = -1 }; /* clang-format on */ typedef struct _usbacm_dev { - struct _usbacm_dev *prev, *next; + idnode_t node; /* node.id is oid.id and ACM id (/dev/usbacm[ID]) */ + usb_devinfo_t instance; /* USB HOST related */ @@ -77,8 +79,6 @@ typedef struct _usbacm_dev { int urbctrl; char path[32]; - unsigned int id; /* ACM id (/dev/usbacm[ID]) */ - int fileId; /* oid.id */ /* opened file state */ int flags; @@ -94,17 +94,17 @@ typedef struct _usbacm_dev { handle_t rxCond; int rxState; + usb_driver_t *drv; + + usb_cdc_line_coding_t line; } usbacm_dev_t; static struct { char msgstack[USBACM_N_MSG_THREADS][1024] __attribute__((aligned(8))); - char ustack[USBACM_N_UMSG_THREADS - 1][2048] __attribute__((aligned(8))); - usbacm_dev_t *devices; - unsigned drvport; + idtree_t devices; unsigned msgport; handle_t lock; - int lastId; } usbacm_common; @@ -150,7 +150,7 @@ static int _usbacm_rxStop(usbacm_dev_t *dev) setup.wLength = 0; dev->rxState = RxStopped; - return usb_transferAsync(dev->pipeCtrl, dev->urbctrl, 0, &setup); + return usb_transferAsync(dev->drv, dev->pipeCtrl, dev->urbctrl, 0, &setup); } @@ -168,18 +168,16 @@ static void usbacm_fifoPush(usbacm_dev_t *dev, const char *data, size_t size) static usbacm_dev_t *usbacm_getByPipe(int pipe) { usbacm_dev_t *tmp, *dev = NULL; + rbnode_t *node; mutexLock(usbacm_common.lock); - tmp = usbacm_common.devices; - if (tmp != NULL) { - do { - if (tmp->pipeBulkIN == pipe || tmp->pipeBulkOUT == pipe || tmp->pipeIntIN == pipe) { - dev = tmp; - dev->rfcnt++; - break; - } - tmp = tmp->next; - } while (tmp != usbacm_common.devices); + for (node = lib_rbMinimum(usbacm_common.devices.root); node != NULL; node = lib_rbNext(node)) { + tmp = lib_treeof(usbacm_dev_t, node, lib_treeof(idnode_t, linkage, node)); + if (tmp->pipeBulkIN == pipe || tmp->pipeBulkOUT == pipe || tmp->pipeIntIN == pipe) { + dev = tmp; + dev->rfcnt++; + break; + } } mutexUnlock(usbacm_common.lock); @@ -189,19 +187,12 @@ static usbacm_dev_t *usbacm_getByPipe(int pipe) static usbacm_dev_t *usbacm_get(int id) { - usbacm_dev_t *tmp, *dev = NULL; + usbacm_dev_t *dev; mutexLock(usbacm_common.lock); - tmp = usbacm_common.devices; - if (tmp != NULL) { - do { - if (tmp->fileId == id) { - dev = tmp; - dev->rfcnt++; - break; - } - tmp = tmp->next; - } while (tmp != usbacm_common.devices); + dev = lib_treeof(usbacm_dev_t, node, idtree_find(&usbacm_common.devices, id)); + if (dev != NULL) { + dev->rfcnt++; } mutexUnlock(usbacm_common.lock); @@ -221,26 +212,10 @@ static void usbacm_free(usbacm_dev_t *dev) } -static void usbacm_freeAll(usbacm_dev_t **devices) -{ - usbacm_dev_t *next, *dev = *devices; - - if (dev != NULL) { - do { - next = dev->next; - usbacm_free(dev); - dev = next; - } while (dev != *devices); - } - - *devices = NULL; -} - - static int _usbacm_put(usbacm_dev_t *dev) { if (--dev->rfcnt == 0) { - LIST_REMOVE(&usbacm_common.devices, dev); + idtree_remove(&usbacm_common.devices, &dev->node); } return dev->rfcnt; } @@ -260,14 +235,28 @@ static void usbacm_put(usbacm_dev_t *dev) } -static void usbacm_handleCompletion(usb_completion_t *c, const char *data, size_t len) +static int usbacm_setLine(usbacm_dev_t *dev) +{ + usb_setup_packet_t setup = { + .bmRequestType = REQUEST_DIR_HOST2DEV | REQUEST_TYPE_CLASS | REQUEST_RECIPIENT_INTERFACE, + .bRequest = 0x20, /* SET_LINE_CODING */ + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(usb_cdc_line_coding_t), + }; + + return usb_transferControl(dev->drv, dev->pipeCtrl, &setup, &dev->line, sizeof(usb_cdc_line_coding_t), usb_dir_out); +} + + +static int usbacm_handleCompletion(usb_driver_t *drv, usb_completion_t *c, const char *data, size_t len) { usbacm_dev_t *dev; size_t bytes; dev = usbacm_getByPipe(c->pipeid); if (dev == NULL) { - return; + return -1; } TRACE("handleCompletion: c->err=%d, len=%u, rxbuflen=%u", c->err, len, dev->rxbuflen); @@ -281,12 +270,12 @@ static void usbacm_handleCompletion(usb_completion_t *c, const char *data, size_ } mutexUnlock(dev->rxLock); usbacm_put(dev); - return; + return -1; } if (c->pipeid != dev->pipeBulkIN) { usbacm_put(dev); - return; + return -1; } if (dev->flags & O_NONBLOCK) { @@ -309,10 +298,12 @@ static void usbacm_handleCompletion(usb_completion_t *c, const char *data, size_ } if (dev->rxState == RxRunning) { - usb_transferAsync(dev->pipeBulkIN, c->urbid, USBACM_BULK_SZ, NULL); + usb_transferAsync(drv, dev->pipeBulkIN, c->urbid, USBACM_BULK_SZ, NULL); } usbacm_put(dev); + + return 0; } @@ -320,7 +311,7 @@ static void usbacm_handleCompletion(usb_completion_t *c, const char *data, size_ static int _usbacm_rxStart(usbacm_dev_t *dev) { usb_setup_packet_t setup; - int i; + int i, ret; setup.bmRequestType = REQUEST_DIR_HOST2DEV | REQUEST_TYPE_CLASS | REQUEST_RECIPIENT_INTERFACE; setup.bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE; @@ -328,12 +319,14 @@ static int _usbacm_rxStart(usbacm_dev_t *dev) setup.wValue = 3; setup.wLength = 0; - if (usb_transferAsync(dev->pipeCtrl, dev->urbctrl, 0, &setup) < 0) { + ret = usb_transferAsync(dev->drv, dev->pipeCtrl, dev->urbctrl, 0, &setup); + if (ret < 0) { return -EIO; } for (i = 0; i < USBACM_N_URBS; i++) { - if (usb_transferAsync(dev->pipeBulkIN, dev->urbs[i], USBACM_BULK_SZ, NULL) < 0) { + ret = usb_transferAsync(dev->drv, dev->pipeBulkIN, dev->urbs[i], USBACM_BULK_SZ, NULL); + if (ret < 0) { return -EIO; } } @@ -400,7 +393,9 @@ static int usbacm_write(usbacm_dev_t *dev, const char *data, size_t len) int ret = 0; TRACE("write: len=%u, flags=%u", len, dev->flags); - if ((ret = usb_transferBulk(dev->pipeBulkOUT, (void *)data, len, usb_dir_out)) <= 0) { + + ret = usb_transferBulk(dev->drv, dev->pipeBulkOUT, (void *)data, len, usb_dir_out); + if (ret <= 0) { fprintf(stderr, "usbacm: write failed\n"); ret = -EIO; } @@ -409,24 +404,22 @@ static int usbacm_write(usbacm_dev_t *dev, const char *data, size_t len) } -static usbacm_dev_t *usbacm_devAlloc(void) +static usbacm_dev_t *_usbacm_devAlloc(void) { usbacm_dev_t *dev; - if ((dev = calloc(1, sizeof(usbacm_dev_t))) == NULL) { + dev = calloc(1, sizeof(usbacm_dev_t)); + if (dev == NULL) { fprintf(stderr, "usbacm: Not enough memory\n"); return NULL; } - /* Get next device number */ - if (usbacm_common.devices == NULL) - dev->id = 0; - else - dev->id = usbacm_common.devices->prev->id + 1; - - dev->fileId = usbacm_common.lastId++; + if (idtree_alloc(&usbacm_common.devices, &dev->node) < 0) { + free(dev); + return NULL; + } - snprintf(dev->path, sizeof(dev->path), "/dev/usbacm%u", dev->id); + dev->rfcnt = 1; return dev; } @@ -436,20 +429,20 @@ static int _usbacm_urbsAlloc(usbacm_dev_t *dev) { int i, j; - dev->urbctrl = usb_urbAlloc(dev->pipeCtrl, NULL, usb_dir_out, 0, usb_transfer_control); + dev->urbctrl = usb_urbAlloc(dev->drv, dev->pipeCtrl, NULL, usb_dir_out, 0, usb_transfer_control); if (dev->urbctrl < 0) { return -1; } for (i = 0; i < USBACM_N_URBS; i++) { - dev->urbs[i] = usb_urbAlloc(dev->pipeBulkIN, NULL, usb_dir_in, USBACM_BULK_SZ, usb_transfer_bulk); + dev->urbs[i] = usb_urbAlloc(dev->drv, dev->pipeBulkIN, NULL, usb_dir_in, USBACM_BULK_SZ, usb_transfer_bulk); if (dev->urbs[i] < 0) { for (j = i - 1; j >= 0; j--) { - usb_urbFree(dev->pipeBulkIN, dev->urbs[j]); + usb_urbFree(dev->drv, dev->pipeBulkIN, dev->urbs[j]); dev->urbs[j] = 0; } - usb_urbFree(dev->pipeCtrl, dev->urbctrl); + usb_urbFree(dev->drv, dev->pipeCtrl, dev->urbctrl); return -1; } } @@ -497,7 +490,7 @@ static void _usbacm_close(usbacm_dev_t *dev) TRACE("close: flags=%u", dev->flags); for (i = 0; i < USBACM_N_URBS; i++) { - usb_urbFree(dev->pipeBulkIN, dev->urbs[i]); + usb_urbFree(dev->drv, dev->pipeBulkIN, dev->urbs[i]); } if (dev->flags & O_NONBLOCK) { @@ -506,7 +499,7 @@ static void _usbacm_close(usbacm_dev_t *dev) mutexUnlock(dev->rxLock); } - usb_urbFree(dev->pipeCtrl, dev->urbctrl); + usb_urbFree(dev->drv, dev->pipeCtrl, dev->urbctrl); dev->flags = 0; dev->clientpid = 0; @@ -573,6 +566,7 @@ static void usbacm_msgthr(void *arg) /* A device can be opened only by one process */ /* FIXME: allow mtClose with different PID (files closing on process exit have invalid PID value as of 2023-10-23) */ if (((msg.type != mtOpen) && (msg.type != mtClose)) && (msg.pid != dev->clientpid)) { + usbacm_put(dev); msg.o.err = -EBUSY; msgRespond(usbacm_common.msgport, &msg, rid); continue; @@ -629,98 +623,140 @@ static void usbacm_msgthr(void *arg) } -static int usbacm_handleInsertion(usb_devinfo_t *insertion) +static int usbacm_handleInsertion(usb_driver_t *drv, usb_devinfo_t *insertion, usb_event_insertion_t *event) { usbacm_dev_t *dev; const usb_modeswitch_t *mode; oid_t oid; + int err; TRACE("handleInsertion"); - if ((mode = usb_modeswitchFind(insertion->descriptor.idVendor, + mode = usb_modeswitchFind(insertion->descriptor.idVendor, insertion->descriptor.idProduct, - modeswitch, sizeof(modeswitch) / sizeof(modeswitch[0]))) != NULL) { - return usb_modeswitchHandle(insertion, mode); + modeswitch, sizeof(modeswitch) / sizeof(modeswitch[0])); + if (mode != NULL) { + return usb_modeswitchHandle(drv, insertion, mode); } - if ((dev = usbacm_devAlloc()) == NULL) + mutexLock(usbacm_common.lock); + + dev = _usbacm_devAlloc(); + if (dev == NULL) { + mutexUnlock(usbacm_common.lock); return -ENOMEM; + } dev->instance = *insertion; + dev->drv = drv; - if ((dev->pipeCtrl = usb_open(insertion, usb_transfer_control, 0)) < 0) { - fprintf(stderr, "usbacm: Fail to open control pipe\n"); - free(dev); - return -EINVAL; - } + do { + dev->pipeCtrl = usb_open(drv, insertion, usb_transfer_control, 0); + if (dev->pipeCtrl < 0) { + fprintf(stderr, "usbacm: Fail to open control pipe\n"); + err = -EINVAL; + break; + } - if (usb_setConfiguration(dev->pipeCtrl, 1) != 0) { - fprintf(stderr, "usbacm: Fail to set configuration\n"); - free(dev); - return -EINVAL; - } + err = usb_setConfiguration(drv, dev->pipeCtrl, 1); + if (err != 0) { + fprintf(stderr, "usbacm: Fail to set configuration\n"); + err = -EINVAL; + break; + } - if ((dev->pipeBulkIN = usb_open(insertion, usb_transfer_bulk, usb_dir_in)) < 0) { - free(dev); - return -EINVAL; - } + dev->pipeBulkIN = usb_open(drv, insertion, usb_transfer_bulk, usb_dir_in); + if (dev->pipeBulkIN < 0) { + err = -EINVAL; + break; + } - if ((dev->pipeBulkOUT = usb_open(insertion, usb_transfer_bulk, usb_dir_out)) < 0) { - free(dev); - return -EINVAL; - } + dev->pipeBulkOUT = usb_open(drv, insertion, usb_transfer_bulk, usb_dir_out); + if (dev->pipeBulkOUT < 0) { + err = -EINVAL; + break; + } - /* Interrupt pipe is optional */ - dev->pipeIntIN = usb_open(insertion, usb_transfer_interrupt, usb_dir_in); + /* Interrupt pipe is optional */ + dev->pipeIntIN = usb_open(drv, insertion, usb_transfer_interrupt, usb_dir_in); - if (mutexCreate(&dev->rxLock) != 0) { - free(dev); - return -ENOMEM; - } + err = mutexCreate(&dev->rxLock); + if (err != 0) { + err = -ENOMEM; + break; + } - if (condCreate(&dev->rxCond) != 0) { - resourceDestroy(dev->rxLock); - free(dev); - return -ENOMEM; - } + err = condCreate(&dev->rxCond); + if (err != 0) { + resourceDestroy(dev->rxLock); + err = -ENOMEM; + break; + } - oid.port = usbacm_common.msgport; - oid.id = dev->fileId; - if (create_dev(&oid, dev->path) != 0) { - resourceDestroy(dev->rxCond); - resourceDestroy(dev->rxLock); - free(dev); - fprintf(stderr, "usbacm: Can't create dev!\n"); - return -EINVAL; + dev->line.dwDTERate = USBACM_SET_LINE_DEFAULT_RATE; + dev->line.bCharFormat = 0; + dev->line.bParityType = 0; + dev->line.bDataBits = 8; + + /* Some broken devices won't function without doing the set line cmd first */ + err = usbacm_setLine(dev); + if (err < 0) { + fprintf(stderr, "usbacm: Set line to speed %d failed/unsupported: %d\n", dev->line.dwDTERate, err); + /* Continue as this is optional: device may reject the setting/not support the set line cmd */ + } + + oid.port = usbacm_common.msgport; + oid.id = idtree_id(&dev->node); + + snprintf(dev->path, sizeof(dev->path), "/dev/usbacm%d", idtree_id(&dev->node)); + + err = create_dev(&oid, dev->path); + if (err != 0) { + resourceDestroy(dev->rxCond); + resourceDestroy(dev->rxLock); + fprintf(stderr, "usbacm: Can't create dev: %s\n", dev->path); + err = -EINVAL; + break; + } + } while (0); + + if (err < 0) { + _usbacm_put(dev); } - dev->rfcnt = 1; - mutexLock(usbacm_common.lock); - LIST_ADD(&usbacm_common.devices, dev); mutexUnlock(usbacm_common.lock); + if (err < 0) { + free(dev); + return err; + } + fprintf(stdout, "usbacm: New device: %s\n", dev->path); + event->deviceCreated = true; + event->dev = oid; + strncpy(event->devPath, dev->path, sizeof(event->devPath)); + return 0; } -static int _usbacm_handleDeletion(usb_deletion_t *del, usbacm_dev_t **devicesToFree) +static int usbacm_handleDeletion(usb_driver_t *drv, usb_deletion_t *del) { - usbacm_dev_t *next, *dev = usbacm_common.devices; - int cont = 1; - - if (dev == NULL) - return 0; + rbnode_t *node, *next; + usbacm_dev_t *dev; TRACE("handleDeletion"); - do { - next = dev->next; + mutexLock(usbacm_common.lock); + + node = lib_rbMinimum(usbacm_common.devices.root); + while (node != NULL) { + next = lib_rbNext(node); + dev = lib_treeof(usbacm_dev_t, node, lib_treeof(idnode_t, linkage, node)); + if (dev->instance.bus == del->bus && dev->instance.dev == del->dev && dev->instance.interface == del->interface) { - if (dev == next) - cont = 0; /* close pending transfers, reject new ones */ mutexLock(dev->rxLock); @@ -729,62 +765,24 @@ static int _usbacm_handleDeletion(usb_deletion_t *del, usbacm_dev_t **devicesToF mutexUnlock(dev->rxLock); if (_usbacm_put(dev) == 0) { - LIST_ADD(devicesToFree, dev); + usbacm_free(dev); } - if (!cont) - break; } - dev = next; - } while (dev != usbacm_common.devices); - - return 0; -} + node = next; + } -static void usbthr(void *arg) -{ - msg_t msg; - usb_msg_t *umsg = (usb_msg_t *)msg.i.raw; - msg_rid_t rid; - usbacm_dev_t *devicesToFree = NULL; - - for (;;) { - if (msgRecv(usbacm_common.drvport, &msg, &rid) < 0) - continue; - - switch (umsg->type) { - case usb_msg_insertion: - usbacm_handleInsertion(&umsg->insertion); - break; - case usb_msg_deletion: - mutexLock(usbacm_common.lock); - _usbacm_handleDeletion(&umsg->deletion, &devicesToFree); - mutexUnlock(usbacm_common.lock); - usbacm_freeAll(&devicesToFree); - break; - case usb_msg_completion: - usbacm_handleCompletion(&umsg->completion, msg.i.data, msg.i.size); - break; - default: - fprintf(stderr, "usbacm: Error when receiving event from host\n"); - break; - } + mutexUnlock(usbacm_common.lock); - msgRespond(usbacm_common.drvport, &msg, rid); - } + return 0; } -int main(int argc, char *argv[]) + +static int usbacm_init(usb_driver_t *drv, void *args) { int ret; int i; - /* Port for communication with the USB stack */ - if (portCreate(&usbacm_common.drvport) != 0) { - fprintf(stderr, "usbacm: Can't create port!\n"); - return 1; - } - /* Port for communication with driver clients */ if (portCreate(&usbacm_common.msgport) != 0) { fprintf(stderr, "usbacm: Can't create port!\n"); @@ -796,12 +794,7 @@ int main(int argc, char *argv[]) return 1; } - if ((usb_connect(filters, sizeof(filters) / sizeof(filters[0]), usbacm_common.drvport)) < 0) { - fprintf(stderr, "usbacm: Fail to connect to usb host!\n"); - return 1; - } - - usbacm_common.lastId = 1; + idtree_init(&usbacm_common.devices); for (i = 0; i < USBACM_N_MSG_THREADS; i++) { ret = beginthread(usbacm_msgthr, USBACM_MSG_PRIO, usbacm_common.msgstack[i], sizeof(usbacm_common.msgstack[i]), NULL); @@ -811,16 +804,35 @@ int main(int argc, char *argv[]) } } - for (i = 0; i < USBACM_N_UMSG_THREADS - 1; i++) { - ret = beginthread(usbthr, USBACM_UMSG_PRIO, usbacm_common.ustack[i], sizeof(usbacm_common.ustack[i]), NULL); - if (ret < 0) { - fprintf(stderr, "usbacm: fail to beginthread ret: %d\n", ret); - return 1; - } - } + return 0; +} - priority(USBACM_UMSG_PRIO); - usbthr(NULL); - return 0; +static int usbacm_destroy(usb_driver_t *drv) +{ + /* TODO */ + return EOK; +} + + +static usb_driver_t usbacm_driver = { + .name = "usbacm", + .handlers = { + .insertion = usbacm_handleInsertion, + .deletion = usbacm_handleDeletion, + .completion = usbacm_handleCompletion, + }, + .ops = { + .init = usbacm_init, + .destroy = usbacm_destroy, + }, + .filters = filters, + .nfilters = sizeof(filters) / sizeof(filters[0]), + .priv = (void *)&usbacm_common, +}; + + +__attribute__((constructor)) static void usbacm_register(void) +{ + usb_driverRegister(&usbacm_driver); } diff --git a/usb/ehci/Makefile b/usb/ehci/Makefile index 8fe36da3c..50875c6bc 100644 --- a/usb/ehci/Makefile +++ b/usb/ehci/Makefile @@ -1,11 +1,15 @@ # -# Makefile for Phoenix-RTOS imx6ull-ehci +# Makefile for Phoenix-RTOS ehci # # Copyright 2018, 2019 Phoenix Systems # # FIXME: rename usb host component NAME := libusbehci -LOCAL_SRCS := ehci.c ehci-hub.c phy-$(TARGET_SUBFAMILY).c +LOCAL_SRCS := ehci.c ehci-hub.c phy-$(TARGET_FAMILY)-$(TARGET_SUBFAMILY).c + +ifneq (,$(findstring imx,$(TARGET_SUBFAMILY))) + LOCAL_CFLAGS += -DEHCI_IMX +endif include $(static-lib.mk) diff --git a/usb/ehci/ehci-hub.c b/usb/ehci/ehci-hub.c index df8f5cc6d..2f34ce70d 100644 --- a/usb/ehci/ehci-hub.c +++ b/usb/ehci/ehci-hub.c @@ -3,8 +3,8 @@ * * ehci root hub implementation * - * Copyright 2021 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -39,7 +39,12 @@ static const struct { .bcdUSB = 0x0200, .bDeviceClass = USB_CLASS_HUB, .bDeviceSubClass = 0, +#ifdef EHCI_IMX + /* imx deviation: the controller has an embedded TT */ .bDeviceProtocol = 1, /* Single TT */ +#else + .bDeviceProtocol = 0, /* Root hub */ +#endif .bMaxPacketSize0 = 64, .idVendor = 0x0, .idProduct = 0x0, @@ -87,17 +92,44 @@ static const struct { static void ehci_resetPort(hcd_t *hcd, int port) { ehci_t *ehci = (ehci_t *)hcd->priv; - volatile int *reg = (hcd->base + portsc1) + (port - 1); + volatile uint32_t *reg = (ehci->opbase + portsc1) + (port - 1); + uint32_t tmp; - *reg &= ~PORTSC_ENA; - *reg |= PORTSC_PR; + log_debug("resetting port %d", port); + + tmp = *reg; + tmp &= ~(PORTSC_ENA | PORTSC_PR); + *reg = tmp | PORTSC_PR; + +#ifdef EHCI_IMX /* - * This is imx deviation. According to ehci documentation + * imx deviation: According to ehci documentation * it is up to software to set the PR bit 0 after waiting 20ms */ while (*reg & PORTSC_PR) ; usleep(20 * 1000); +#else + /* Wait for reset to complete */ + usleep(50 * 1000); + + /* Stop the reset sequence */ + *reg = tmp; + + /* Wait until reset sequence stops */ + while ((*reg & PORTSC_PR) != 0) + ; + + usleep(20 * 1000); + + tmp = *reg; + + log_debug("port %d reset done, status after reset=%x", port, tmp); + + if ((tmp & PORTSC_ENA) == 0) { + log_debug("device on port %d is not a highspeed device", port); + } +#endif ehci->portResetChange = 1 << port; @@ -110,7 +142,7 @@ static int ehci_getPortStatus(usb_dev_t *hub, int port, usb_port_status_t *statu { hcd_t *hcd = hub->hcd; ehci_t *ehci = (ehci_t *)hcd->priv; - int val; + uint32_t val; if (port > hub->nports) return -1; @@ -118,7 +150,7 @@ static int ehci_getPortStatus(usb_dev_t *hub, int port, usb_port_status_t *statu status->wPortChange = 0; status->wPortStatus = 0; - val = *(hcd->base + portsc1 + port - 1); + val = *(ehci->opbase + portsc1 + port - 1); if (val & PORTSC_CCS) status->wPortStatus |= USB_PORT_STAT_CONNECTION; @@ -152,10 +184,15 @@ static int ehci_getPortStatus(usb_dev_t *hub, int port, usb_port_status_t *statu if (ehci->portResetChange & (1 << port)) status->wPortChange |= USB_PORT_STAT_C_RESET; +#ifdef EHCI_IMX if ((val & PORTSC_PSPD) >> 26 == 1) status->wPortStatus |= USB_PORT_STAT_LOW_SPEED; else if ((val & PORTSC_PSPD) >> 26 == 2) status->wPortStatus |= USB_PORT_STAT_HIGH_SPEED; +#else + /* TODO handle low/full speed devices on ia32 */ + status->wPortStatus |= USB_PORT_STAT_HIGH_SPEED; +#endif /* TODO: set indicator */ @@ -192,7 +229,7 @@ static int ehci_clearPortFeature(usb_dev_t *hub, int port, uint16_t wValue) { hcd_t *hcd = hub->hcd; ehci_t *ehci = (ehci_t *)hcd->priv; - volatile int *portsc = hcd->base + portsc1 + port - 1; + volatile uint32_t *portsc = (ehci->opbase + portsc1) + (port - 1); uint32_t val = *portsc; if (port > hub->nports) @@ -285,6 +322,7 @@ static int ehci_getStringDesc(usb_dev_t *hub, int index, char *buf, size_t size) static int ehci_getDesc(usb_dev_t *hub, int type, int index, char *buf, size_t size) { hcd_t *hcd = hub->hcd; + ehci_t *ehci = (ehci_t *)hcd->priv; usb_hub_desc_t *hdesc; int bytes = 0; @@ -309,7 +347,7 @@ static int ehci_getDesc(usb_dev_t *hub, int type, int index, char *buf, size_t s hdesc->wHubCharacteristics = 0x1; hdesc->bPwrOn2PwrGood = 10; hdesc->bHubContrCurrent = 10; - hdesc->bNbrPorts = *(hcd->base + hcsparams) & 0xf; + hdesc->bNbrPorts = *(ehci->base + hcsparams) & 0xf; hdesc->variable[0] = 0; /* Device not removable */ hdesc->variable[1] = 0xff; /* PortPwrCtrlMask */ break; @@ -322,16 +360,18 @@ static int ehci_getDesc(usb_dev_t *hub, int type, int index, char *buf, size_t s uint32_t ehci_getHubStatus(usb_dev_t *hub) { hcd_t *hcd = hub->hcd; - uint32_t status = 0; - int i, val; + uint32_t status = 0, val; + int i; ehci_t *ehci = (ehci_t *)hcd->priv; for (i = 0; i < hub->nports; i++) { - val = ehci->portsc; + val = *(ehci->opbase + portsc1 + i); + log_debug("(INT%d) port %d portsc: %x", hcd->info->irq, i + 1, val); if (val & (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC)) status |= 1 << (i + 1); } + log_debug("(INT%d): status: %x", hcd->info->irq, status); return status; } @@ -340,12 +380,13 @@ int ehci_roothubReq(usb_dev_t *hub, usb_transfer_t *t) { usb_setup_packet_t *setup = t->setup; int ret; + ehci_t *ehci = (ehci_t *)hub->hcd->priv; /* It will be finished, when a port status changes */ if (t->type == usb_transfer_interrupt) { /* Enable Port Status Changed interrupt if this is a first call */ - if ((*(hub->hcd->base + usbintr) & USBSTS_PCI) == 0) - *(hub->hcd->base + usbintr) |= USBSTS_PCI; + if ((*(ehci->opbase + usbintr) & USBSTS_PCI) == 0) + *(ehci->opbase + usbintr) |= USBSTS_PCI; return 0; } diff --git a/usb/ehci/ehci.c b/usb/ehci/ehci.c index a947f67c6..1033d9e4e 100644 --- a/usb/ehci/ehci.c +++ b/usb/ehci/ehci.c @@ -5,9 +5,9 @@ * * ehci/ehci.c * - * Copyright 2018, 2021 Phoenix Systems + * Copyright 2018, 2021, 2024 Phoenix Systems * Copyright 2007 Pawel Pisarczyk - * Author: Jan Sikorski, Maciej Purski + * Author: Jan Sikorski, Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,11 @@ #include "ehci.h" +#ifdef EHCI_IMX #define EHCI_PERIODIC_SIZE 128 +#else +#define EHCI_PERIODIC_SIZE 1024 +#endif #ifndef EHCI_PRIO #define EHCI_PRIO 2 @@ -45,7 +50,11 @@ static inline void ehci_memDmb(void) { - asm volatile ("dmb" ::: "memory"); +#ifdef EHCI_IMX + asm volatile("dmb" ::: "memory"); +#else + __sync_synchronize(); +#endif } @@ -53,19 +62,21 @@ static void ehci_startAsync(hcd_t *hcd) { ehci_t *ehci = (ehci_t *)hcd->priv; - *(hcd->base + asynclistaddr) = va2pa((void *)ehci->asyncList->hw); - *(hcd->base + usbcmd) |= USBCMD_ASE; + *(ehci->opbase + asynclistaddr) = va2pa((void *)ehci->asyncList->hw); + *(ehci->opbase + usbcmd) |= USBCMD_ASE; ehci_memDmb(); - while ((*(hcd->base + usbsts) & USBSTS_AS) == 0) + while ((*(ehci->opbase + usbsts) & USBSTS_AS) == 0) ; } static void ehci_stopAsync(hcd_t *hcd) { - *(hcd->base + usbcmd) &= ~USBCMD_ASE; + ehci_t *ehci = (ehci_t *)hcd->priv; + + *(ehci->opbase + usbcmd) &= ~USBCMD_ASE; ehci_memDmb(); - while ((*(hcd->base + usbsts) & USBSTS_AS) != 0) + while ((*(ehci->opbase + usbsts) & USBSTS_AS) != 0) ; } @@ -160,6 +171,34 @@ static void ehci_qtdsPut(ehci_t *ehci, ehci_qtd_t **head) } +static void +ehci_qtdDump(ehci_qtd_t *qtd, bool dump_bufs) +{ +#if EHCI_DEBUG_QTD + uint32_t s; + + s = qtd->hw->token; + fprintf(stderr, "sts=0x%08x: tog=%d sz=0x%x ioc=%d", + s, s >> 31, (s >> 16) & 0x7fff, + (s >> 15) & 0b1); + fprintf(stderr, " cerr=%d pid=%d %s%s%s%s%s%s%s%s\n", + (s >> 10) & 0b11, (s >> 8) & 0b11, + (s & QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", + (s & QTD_HALTED) ? "-HALTED" : "", + (s & QTD_BUFERR) ? "-BUFERR" : "", + (s & QTD_BABBLE) ? "-BABBLE" : "", + (s & QTD_XACT) ? "-XACT" : "", + (s & QTD_MISSED_UFRAME) ? "-MISSED" : "", + (s & QTD_SPLIT) ? "-SPLIT" : "", + (s & QTD_PING) ? "-PING" : ""); + + for (s = 0; dump_bufs && s < EHCI_QH_NBUFS; s++) { + fprintf(stderr, " buf[%d]=0x%08x buf_hi[%d]=0x%08x\n", s, qtd->hw->buf[s], s, qtd->hw->buf_hi[s]); + } +#endif +} + + static ehci_qtd_t *ehci_qtdAlloc(ehci_t *ehci, int pid, size_t maxpacksz, char *data, size_t *size, int datax) { ehci_qtd_t *qtd; @@ -185,21 +224,30 @@ static ehci_qtd_t *ehci_qtdAlloc(ehci_t *ehci, int pid, size_t maxpacksz, char * if (data != NULL) { qtd->hw->buf[0] = (uintptr_t)va2pa(data); + qtd->hw->buf_hi[0] = 0; + offs = min(EHCI_PAGE_SIZE - QTD_OFFSET(qtd->hw->buf[0]), *size); bytes += offs; data += offs; - for (i = 1; i < 5 && bytes != *size; i++) { + for (i = 1; i < EHCI_QH_NBUFS && bytes != *size; i++) { qtd->hw->buf[i] = va2pa(data) & ~0xfff; + qtd->hw->buf_hi[i] = 0; + offs = min(*size - bytes, EHCI_PAGE_SIZE); /* If the data does not fit one qtd, don't leave a trailing short packet */ - if (i == 4 && bytes + offs < *size) + if (i == EHCI_QH_NBUFS - 1 && bytes + offs < *size) offs = (((bytes + offs) / maxpacksz) * maxpacksz) - bytes; bytes += offs; data += offs; } + for (; i < EHCI_QH_NBUFS; i++) { + qtd->hw->buf[i] = 0; + qtd->hw->buf_hi[i] = 0; + } + qtd->hw->token |= bytes << 16; *size -= bytes; } @@ -248,6 +296,7 @@ static void ehci_qhPut(ehci_t *ehci, ehci_qh_t *qh) static ehci_qh_t *ehci_qhAlloc(ehci_t *ehci) { ehci_qh_t *qh; + int i; if ((qh = ehci_qhGet(ehci)) == NULL) { if ((qh = malloc(sizeof(ehci_qh_t))) == NULL) @@ -274,6 +323,11 @@ static ehci_qh_t *ehci_qhAlloc(ehci_t *ehci) qh->phase = 0; qh->lastQtd = NULL; + for (i = 0; i < EHCI_QH_NBUFS; i++) { + qh->hw->buf[i] = 0; + qh->hw->buf_hi[i] = 0; + } + return qh; } @@ -494,20 +548,17 @@ static int ehci_irqHandler(unsigned int n, void *data) ehci_t *ehci = (ehci_t *)hcd->priv; uint32_t currentStatus; - currentStatus = *(hcd->base + usbsts); + currentStatus = *(ehci->opbase + usbsts); do { - *(hcd->base + usbsts) = currentStatus & EHCI_INTRMASK; + *(ehci->opbase + usbsts) = currentStatus & (EHCI_INTRMASK | USBSTS_FRI); ehci->status |= currentStatus; /* For edge triggered interrupts to prevent losing interrupts, * poll the usbsts register until it is stable */ - currentStatus = *(hcd->base + usbsts); + currentStatus = *(ehci->opbase + usbsts); } while ((currentStatus & EHCI_INTRMASK) != 0); - if (ehci->status & USBSTS_PCI) - ehci->portsc = *(hcd->base + portsc1); - return -!(ehci->status & EHCI_INTRMASK); } @@ -520,8 +571,10 @@ static int ehci_qtdsCheck(hcd_t *hcd, usb_transfer_t *t, int *status) *status = 0; do { - if (qtds->hw->token & (QTD_XACT | QTD_BABBLE | QTD_BUFERR)) + ehci_qtdDump(qtds, false); + if (qtds->hw->token & (QTD_XACT | QTD_BABBLE | QTD_BUFERR | QTD_HALTED)) { error++; + } qtds = qtds->next; } while (qtds != t->hcdpriv); @@ -585,6 +638,29 @@ static void ehci_portStatusChanged(hcd_t *hcd) } +#if EHCI_DEBUG_IRQ +static void ehci_printIrq(hcd_t *hcd) +{ + ehci_t *ehci = (ehci_t *)hcd->priv; + static char buf[30]; + size_t i = 0; + + i += sprintf(buf, "INT%d: ", hcd->info->irq); + +#define append_to_buf(interrupt) \ + if (ehci->status & (interrupt)) { \ + i += sprintf(buf + i, #interrupt " "); \ + } + append_to_buf(USBSTS_UI); + append_to_buf(USBSTS_UEI); + append_to_buf(USBSTS_SEI); + append_to_buf(USBSTS_PCI); + + log_debug("%s", buf); +} +#endif + + static void ehci_irqThread(void *arg) { hcd_t *hcd = (hcd_t *)arg; @@ -594,14 +670,31 @@ static void ehci_irqThread(void *arg) for (;;) { condWait(ehci->irqCond, ehci->irqLock, 0); +#if EHCI_DEBUG_IRQ + ehci_printIrq(hcd); +#endif + + /* The irqThread must clear the handler interrupt status, + since otherwise it would handle ghost interrupts + on every interrupt (irqHandler never clears ehci->status) */ + if (ehci->status & USBSTS_SEI) { + ehci->status &= ~USBSTS_SEI; + log_error("host system error, controller halted"); + /* TODO cleanup/reset after death */ + continue; + } + if (ehci->status & (USBSTS_UI | USBSTS_UEI)) { + ehci->status &= ~(USBSTS_UI | USBSTS_UEI); mutexLock(hcd->transLock); ehci_transUpdate(hcd); mutexUnlock(hcd->transLock); } - if (ehci->status & USBSTS_PCI) + if (ehci->status & USBSTS_PCI) { + ehci->status &= ~USBSTS_PCI; ehci_portStatusChanged(hcd); + } } } @@ -616,7 +709,7 @@ static int ehci_qtdAdd(ehci_t *ehci, ehci_qtd_t **list, int token, size_t maxpac return -ENOMEM; LIST_ADD(list, tmp); - dt = !dt; + dt = 1 - dt; } while (remaining > 0); return 0; @@ -677,7 +770,7 @@ static int ehci_transferEnqueue(hcd_t *hcd, usb_transfer_t *t, usb_pipe_t *pipe) /* Data stage */ if ((t->type == usb_transfer_control && t->size > 0) || t->type == usb_transfer_bulk || - t->type == usb_transfer_interrupt) { + t->type == usb_transfer_interrupt) { if (ehci_qtdAdd(hcd->priv, &qtds, token, pipe->maxPacketLen, t->buffer, t->size, 1) < 0) { ehci_qtdsPut(hcd->priv, &qtds); t->hcdpriv = NULL; @@ -775,21 +868,21 @@ static int ehci_init(hcd_t *hcd) { ehci_t *ehci; ehci_qh_t *qh; - int i; + int i, ret; if ((ehci = calloc(1, sizeof(ehci_t))) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); return -ENOMEM; } if ((ehci->periodicList = usb_allocAligned(EHCI_PERIODIC_SIZE * sizeof(uint32_t), EHCI_PERIODIC_ALIGN)) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if ((ehci->periodicNodes = calloc(EHCI_PERIODIC_SIZE, sizeof(ehci_qh_t *))) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -797,31 +890,31 @@ static int ehci_init(hcd_t *hcd) hcd->priv = ehci; if (phy_init(hcd) != 0) { - fprintf(stderr, "ehci: Phy init failed!\n"); + log_error("Phy init failed!"); ehci_free(ehci); return -EINVAL; } if (condCreate(&ehci->irqCond) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->irqLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->asyncLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->periodicLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -829,7 +922,7 @@ static int ehci_init(hcd_t *hcd) /* Initialize Async List with a dummy qh to optimize * accesses and make them safer */ if ((qh = ehci_qhAlloc(ehci)) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -840,34 +933,89 @@ static int ehci_init(hcd_t *hcd) for (i = 0; i < EHCI_PERIODIC_SIZE; ++i) ehci->periodicList[i] = QH_PTR_INVALID; + if (((addr_t)hcd->base & (0x20 - 1)) != 0) { + log_error("USBBASE not aligned to 32 bits"); + ehci_free(ehci); + return -EINVAL; + } + + /* Set USBBASE */ + ehci->base = hcd->base; + +#ifdef EHCI_IMX + /* imx deviation: Here we don't distinguish between base/opbase addresses, as + * the distance between operational register base and USBBASE is a known + * constant accounted for in the register enum already. */ + ehci->opbase = ehci->base; +#else + /* In general, EHCI states that the operational register base has address: + * USBBASE + CAPLENGTH */ + ehci->opbase = (volatile uint32_t *)((char *)ehci->base + *(uint8_t *)(ehci->base + caplength)); +#endif + + log_debug("attaching handler to irq=%d", hcd->info->irq); + ret = interrupt(hcd->info->irq, ehci_irqHandler, hcd, ehci->irqCond, &ehci->irqHandle); + + if (ret < 0) { + log_error("failed to set interrupt handler"); + return ret; + } + if (beginthread(ehci_irqThread, EHCI_PRIO, ehci->stack, sizeof(ehci->stack), hcd) != 0) { ehci_free(ehci); return -ENOMEM; } - interrupt(hcd->info->irq, ehci_irqHandler, hcd, ehci->irqCond, &ehci->irqHandle); + +#ifndef EHCI_IMX + /* Hangs controller on imx */ + *(ehci->opbase + usbcmd) &= ~(USBCMD_RUN | USBCMD_IAA); + + while ((*(ehci->opbase + usbsts) & USBSTS_HCH) == 0) + ; +#endif /* Reset controller */ - *(hcd->base + usbcmd) |= 2; - while (*(hcd->base + usbcmd) & 2) + *(ehci->opbase + usbcmd) |= USBCMD_HCRESET; + while ((*(ehci->opbase + usbcmd) & USBCMD_HCRESET) != 0) ; - /* Set host mode */ - *(hcd->base + usbmode) |= 3; +#ifdef EHCI_IMX + /* imx deviation: Set host mode */ + *(ehci->opbase + usbmode) |= 3; +#else + if ((*(ehci->base + hccparams) & HCCPARAMS_64BIT_ADDRS) != 0) { + *(ehci->opbase + ctrldssegment) = 0; + } +#endif /* Enable interrupts */ - *(hcd->base + usbintr) = USBSTS_UI | USBSTS_UEI; + *(ehci->opbase + usbintr) = USBSTS_UI | USBSTS_UEI | USBSTS_SEI; /* Set periodic frame list */ - *(hcd->base + periodiclistbase) = va2pa(ehci->periodicList); + *(ehci->opbase + periodiclistbase) = va2pa(ehci->periodicList); + +#ifdef EHCI_IMX + /* imx deviation: Set frame list size (128 elements) */ + *(ehci->opbase + usbcmd) |= (3 << 2); +#endif - /* Set interrupts threshold, frame list size - 128 bytes, turn controller on */ - *(hcd->base + usbcmd) |= (1 << 4) | (3 << 2) | 1; + /* Turn the controller on, enable periodic scheduling */ + *(ehci->opbase + usbcmd) &= ~(USBCMD_LRESET | USBCMD_ASE); + + *(ehci->opbase + usbcmd) |= (USBCMD_PSE | USBCMD_RUN); + while ((*(ehci->opbase + usbsts) & (USBSTS_HCH)) != 0) + ; /* Route all ports to this host controller */ - *(hcd->base + configflag) = 1; + *(ehci->opbase + configflag) = 1; + + /* Allow for the hardware to catch up */ + usleep(50 * 1000); ehci_startAsync(hcd); + log_debug("hc initialized"); + return 0; } diff --git a/usb/ehci/ehci.h b/usb/ehci/ehci.h index e8dd87ad8..06203adcb 100644 --- a/usb/ehci/ehci.h +++ b/usb/ehci/ehci.h @@ -3,8 +3,8 @@ * * USB EHCI host controller * - * Copyright 2021 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -14,6 +14,18 @@ #ifndef _USB_EHCI_H_ #define _USB_EHCI_H_ +#define EHCI_DEBUG 0 +#define EHCI_DEBUG_IRQ 0 +#define EHCI_DEBUG_QTD 0 + +#define LOG_TAG "ehci: " + +/* clang-format off */ +#define log_msg(fmt, ...) do { fprintf(stderr, LOG_TAG fmt "\n", ##__VA_ARGS__); } while (0) +#define log_error(fmt, ...) do { log_msg("error: " fmt, ##__VA_ARGS__); } while (0) +#define log_debug(fmt, ...) do { if (EHCI_DEBUG != 0) log_msg(fmt, ##__VA_ARGS__); } while (0) +/* clang-format on */ + #define USBSTS_AS (1 << 15) #define USBSTS_PS (1 << 14) #define USBSTS_RCL (1 << 13) @@ -29,10 +41,14 @@ #define USBSTS_UEI (1 << 1) #define USBSTS_UI (1 << 0) -#define EHCI_INTRMASK (USBSTS_PCI | USBSTS_UEI | USBSTS_UI) +#define EHCI_INTRMASK (USBSTS_SEI | USBSTS_PCI | USBSTS_UEI | USBSTS_UI) -#define USBCMD_ASE (1 << 5) -#define USBCMD_IAA (1 << 6) +#define USBCMD_RUN (1 << 0) +#define USBCMD_HCRESET (1 << 1) +#define USBCMD_PSE (1 << 4) +#define USBCMD_ASE (1 << 5) +#define USBCMD_IAA (1 << 6) +#define USBCMD_LRESET (1 << 7) #define PORTSC_PTS_1 (3 << 30) #define PORTSC_STS (1 << 29) @@ -108,6 +124,8 @@ /* 'change' bits cleared by writing 1 */ #define PORTSC_CBITS (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC) +#define HCCPARAMS_64BIT_ADDRS (1 << 0) + #define EHCI_PAGE_SIZE 4096 #define EHCI_PERIODIC_ALIGN 4096 @@ -116,6 +134,9 @@ #define EHCI_MAX_QTD_POOL 20 #define EHCI_MAX_QH_POOL 10 + +/* clang-format off */ +#ifdef EHCI_IMX enum { /* identification regs */ id = 0x0, hwgeneral, hwhost, hwdevice, hwtxbuf, hwrxbuf, @@ -136,20 +157,40 @@ enum { endptctrl2, endptctrl3, endptctrl4, endptctrl5, endptctrl6, endptctrl7, }; - enum { usb_otg1_ctrl = 0x200, usb_otg2_ctrl, usb_otg1_phy_ctrl = usb_otg2_ctrl + 5, usb_otg2_phy_ctrl }; enum { ehci_item_itd = 0, ehci_item_qh, ehci_item_sitd, ehci_item_fstn }; +#else +enum { + /* capability regs */ + caplength = 0x0, hciversion = 0x0, hcsparams, hccparams, + hcspportroute1, hcspportroute2 /* hcspportroute is a 64-bit register */ +}; +enum { + /* operational regs */ + usbcmd = 0x0, usbsts, usbintr, frindex, ctrldssegment, + periodiclistbase = 0x5, asynclistaddr, + configflag = 0x10, portsc1 +}; +#endif +/* clang-format on */ + +/* TODO: buf_hi is required only on ia32 if hcd is capable of 64-bit addressing + * Shrink it on smaller targets to save memory? */ struct qtd { uint32_t next; uint32_t altnext; uint32_t token; uint32_t buf[5]; + uint32_t buf_hi[5]; }; +#define EHCI_QH_NBUFS 5 + + struct qh { uint32_t horizontal; uint32_t info[2]; @@ -159,7 +200,8 @@ struct qh { uint32_t nextQtd; uint32_t altnextQtd; uint32_t token; - uint32_t buf[5]; + uint32_t buf[EHCI_QH_NBUFS]; + uint32_t buf_hi[EHCI_QH_NBUFS]; }; @@ -184,6 +226,7 @@ typedef struct _ehci_qh { typedef struct { char stack[1024] __attribute__((aligned(8))); + uint32_t *periodicList; ehci_qh_t *asyncList; ehci_qh_t **periodicNodes; @@ -196,7 +239,9 @@ typedef struct { handle_t irqCond, irqHandle, irqLock, asyncLock, periodicLock; volatile unsigned portResetChange; volatile unsigned status; - volatile unsigned portsc; + + volatile uint32_t *base; + volatile uint32_t *opbase; } ehci_t; diff --git a/usb/ehci/phy-imx6ull.c b/usb/ehci/phy-armv7a7-imx6ull.c similarity index 92% rename from usb/ehci/phy-imx6ull.c rename to usb/ehci/phy-armv7a7-imx6ull.c index 011d2b3f9..fb8abc4d0 100644 --- a/usb/ehci/phy-imx6ull.c +++ b/usb/ehci/phy-armv7a7-imx6ull.c @@ -33,13 +33,13 @@ enum { phy_pwd, phy_pwd_set, phy_pwd_clr, phy_pwd_tog, phy_tx, phy_tx_set, /* NOTE: This should be obtained using device tree */ static const hcd_info_t imx6ull_info[] = { - { - .type = "ehci", + { .type = "ehci", .hcdaddr = 0x02184200, - .phyaddr = 0x020ca000, - .clk = pctl_clk_usboh3, - .irq = 74 - } + .phy = { + .addr = 0x020ca000, + .clk = pctl_clk_usboh3, + }, + .irq = 74 } }; @@ -88,7 +88,7 @@ void phy_initClock(hcd_t *hcd) .action = pctl_set, .type = pctl_devclock, .devclock = { - .dev = hcd->info->clk, + .dev = hcd->info->phy.clk, .state = 3, } }; @@ -103,7 +103,7 @@ void phy_disableClock(hcd_t *hcd) .action = pctl_set, .type = pctl_devclock, .devclock = { - .dev = hcd->info->clk, + .dev = hcd->info->phy.clk, .state = 0, } }; @@ -123,8 +123,8 @@ int phy_init(hcd_t *hcd) { off_t offs; - offs = hcd->info->phyaddr % _PAGE_SIZE; - hcd->phybase = mmap(NULL, _PAGE_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->phyaddr - offs); + offs = hcd->info->phy.addr % _PAGE_SIZE; + hcd->phybase = mmap(NULL, _PAGE_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->phy.addr - offs); if (hcd->phybase == MAP_FAILED) return -ENOMEM; hcd->phybase += (offs / sizeof(int)); diff --git a/usb/ehci/phy-imxrt106x.c b/usb/ehci/phy-armv7m7-imxrt106x.c similarity index 91% rename from usb/ehci/phy-imxrt106x.c rename to usb/ehci/phy-armv7m7-imxrt106x.c index dec0ec339..67fbc68c9 100644 --- a/usb/ehci/phy-imxrt106x.c +++ b/usb/ehci/phy-armv7m7-imxrt106x.c @@ -32,13 +32,13 @@ enum { phy_pwd, phy_pwd_set, phy_pwd_clr, phy_pwd_tog, phy_tx, phy_tx_set, /* NOTE: This should be later implemented using device tree */ static const hcd_info_t imxrt_info[] = { - { - .type = "ehci", + { .type = "ehci", .hcdaddr = 0x402e0200, - .phyaddr = 0x400da000, - .clk = pctl_clk_usboh3, - .irq = 128 - } + .phy = { + .addr = 0x400da000, + .clk = pctl_clk_usboh3, + }, + .irq = 128 } }; @@ -104,10 +104,10 @@ void phy_enableHighSpeedDisconnect(hcd_t *hcd, int enable) int phy_init(hcd_t *hcd) { /* No mmapping, since we are on NOMMU architecture */ - hcd->phybase = (volatile int *)hcd->info->phyaddr; - hcd->base = (volatile int *)hcd->info->hcdaddr; + hcd->phybase = (volatile uint32_t *)hcd->info->phy.addr; + hcd->base = (volatile uint32_t *)hcd->info->hcdaddr; - setClock(hcd->info->clk, clk_state_run); + setClock(hcd->info->phy.clk, clk_state_run); phy_reset(hcd); phy_config(hcd); diff --git a/usb/ehci/phy-imxrt117x.c b/usb/ehci/phy-armv7m7-imxrt117x.c similarity index 95% rename from usb/ehci/phy-imxrt117x.c rename to usb/ehci/phy-armv7m7-imxrt117x.c index 9b6ebcdf7..f1698e283 100644 --- a/usb/ehci/phy-imxrt117x.c +++ b/usb/ehci/phy-armv7m7-imxrt117x.c @@ -40,8 +40,10 @@ static const hcd_info_t imxrt_info[] = { { .type = "ehci", .hcdaddr = 0x40430000, - .phyaddr = 0x40434000, - .clk = pctl_lpcg_usb, + .phy = { + .addr = 0x40434000, + .clk = pctl_lpcg_usb, + }, .irq = usb_otg1_irq, }, #endif @@ -49,8 +51,10 @@ static const hcd_info_t imxrt_info[] = { { .type = "ehci", .hcdaddr = 0x4042c000, - .phyaddr = 0x40438000, - .clk = pctl_lpcg_usb, + .phy = { + .addr = 0x40438000, + .clk = pctl_lpcg_usb, + }, .irq = usb_otg2_irq, } #endif @@ -165,13 +169,13 @@ int phy_init(hcd_t *hcd) return -ENODEV; } - res = setClock(hcd->info->clk, 1); + res = setClock(hcd->info->phy.clk, 1); if (res < 0) { return res; } /* NOMMU architecture, mmap not needed */ - hcd->phybase = (void *)hcd->info->phyaddr; + hcd->phybase = (void *)hcd->info->phy.addr; hcd->base = (void *)hcd->info->hcdaddr; phy_start(hcd); diff --git a/usb/ehci/phy-ia32-generic.c b/usb/ehci/phy-ia32-generic.c new file mode 100644 index 000000000..2d4ab92ce --- /dev/null +++ b/usb/ehci/phy-ia32-generic.c @@ -0,0 +1,179 @@ +/* + * Phoenix-RTOS + * + * EHCI USB Physical Layer for ia32 + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define EHCI_MAP_SIZE (0x1000) + +#define EHCI_HCD_CLASS (0x0c03) /* Serial bus controller / USB controller */ +#define EHCI_HCD_PROGIF (0x20) /* EHCI */ + +#define EHCI_HCCPARAMS_EECP (0x8) + + +int hcd_getInfo(const hcd_info_t **info) +{ + platformctl_t pctl; + int i, err; + + hcd_info_t *hcd_info = NULL; + + pctl.action = pctl_get; + pctl.type = pctl_pci; + pctl.pci.id.cl = EHCI_HCD_CLASS; + pctl.pci.id.progif = EHCI_HCD_PROGIF; + pctl.pci.dev.bus = 0; + pctl.pci.dev.dev = 0; + pctl.pci.dev.func = 0; + pctl.pci.caps = NULL; + + i = 0; + for (;;) { + pctl.pci.id.vendor = PCI_VENDOR_INTEL; + pctl.pci.id.device = PCI_ANY; + pctl.pci.id.subvendor = PCI_ANY; + pctl.pci.id.subdevice = PCI_ANY; + + err = platformctl(&pctl); + + if (err == -ENODEV) { + break; + } + + if (err < 0) { + fprintf(stderr, "phy: pctl_get failed\n"); + return err; + } + + if (pctl.pci.dev.progif != EHCI_HCD_PROGIF) { + pctl.pci.dev.func++; + continue; + } + + hcd_info = realloc(hcd_info, sizeof(hcd_info_t) * (i + 1)); + if (hcd_info == NULL) { + return -ENOMEM; + } + memset((void *)(hcd_info + i), 0, sizeof(hcd_info_t)); + + sprintf(hcd_info[i].type, "ehci"); + hcd_info[i].hcdaddr = pctl.pci.dev.resources[0].base; + + /* TODO do ACPI _PRT lookup instead */ + fprintf(stderr, "phy: choosing default irq from pci\n"); + hcd_info[i].irq = pctl.pci.dev.irq; + + hcd_info[i].pci_devId.bus = pctl.pci.dev.bus; + hcd_info[i].pci_devId.dev = pctl.pci.dev.dev; + hcd_info[i].pci_devId.func = pctl.pci.dev.func; + + i++; + pctl.pci.dev.func++; + } + + *info = hcd_info; + + return i; +} + + +static void phy_config(hcd_t *hcd) +{ + platformctl_t pctl; + int err; + uint8_t eecp; + short bus = hcd->info->pci_devId.bus; + short dev = hcd->info->pci_devId.dev; + short func = hcd->info->pci_devId.func; + + pctl.action = pctl_set; + pctl.type = pctl_pcicfg; + pctl.pcicfg.dev.bus = bus; + pctl.pcicfg.dev.dev = dev; + pctl.pcicfg.dev.func = func; + pctl.pcicfg.cfg = pci_cfg_busmaster; + pctl.pcicfg.enable = 1; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: setting busmaster failed: %d\n", err); + } + + pctl.pcicfg.cfg = pci_cfg_memoryspace; + pctl.pcicfg.enable = 1; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: setting memoryspace failed: %d\n", err); + } + + pctl.pcicfg.cfg = pci_cfg_interruptdisable; + pctl.pcicfg.enable = 0; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: enabling interrupts failed: %d\n", err); + } + + eecp = (*(uint32_t *)((char *)hcd->base + EHCI_HCCPARAMS_EECP) >> 8) & 0xFF; + + if (eecp >= 0x40) { + /* Take ownership of the controller from BIOS */ + pctl.action = pctl_set; + pctl.type = pctl_usbownership; + pctl.usbownership.dev.bus = bus; + pctl.usbownership.dev.dev = dev; + pctl.usbownership.dev.func = func; + pctl.usbownership.osOwned = 1; + pctl.usbownership.eecp = eecp; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: taking controller ownership from BIOS failed: %d\n", err); + } + } +} + + +void phy_enableHighSpeedDisconnect(hcd_t *hcd, int enable) +{ +} + + +int phy_init(hcd_t *hcd) +{ + off_t offs; + + hcd->phybase = NULL; + + offs = hcd->info->hcdaddr % _PAGE_SIZE; + hcd->base = mmap(NULL, EHCI_MAP_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->hcdaddr - offs); + if (hcd->base == MAP_FAILED) { + return -ENOMEM; + } + hcd->base += (offs / sizeof(int)); + + phy_config(hcd); + + return 0; +}