/* $OpenBSD: usbf.c,v 1.11 2008/06/10 20:50:19 miod Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * USB 2.0 logical device driver * * Specification non-comformities: * * - not all Standard Device Requests are supported (see 9.4) * - USB 2.0 devices (device_descriptor.bcdUSB >= 0x0200) must support * the other_speed requests but we do not * * Missing functionality: * * - isochronous pipes/transfers * - clever, automatic endpoint address assignment to make optimal use * of available hardware endpoints * - alternate settings for interfaces are unsupported */ /* * The source code below is marked an can be split into a number of pieces * (in that order): * * - USB logical device match/attach/detach * - USB device tasks * - Bus event handling * - Device request handling * * Stylistic issues: * * - "endpoint number" and "endpoint address" are sometimes confused in * this source code, OTOH the endpoint number is just the address, aside * from the direction bit that is added to the number to form a unique * endpoint address */ #include <sys/param.h> #include <sys/device.h> #include <sys/kthread.h> #include <sys/malloc.h> #include <sys/systm.h> #include <machine/bus.h> #include <dev/usb/usb.h> #include <dev/usb/usbdi.h> #include <dev/usb/usbdivar.h> #include <dev/usb/usbf.h> #include <dev/usb/usbfvar.h> #ifndef USBF_DEBUG #define DPRINTF(l, x) do {} while (0) #else int usbfdebug = 0; #define DPRINTF(l, x) if ((l) <= usbfdebug) printf x; else {} #endif struct usbf_softc { struct device sc_dev; /* base device */ usbf_bus_handle sc_bus; /* USB device controller */ struct usbf_port sc_port; /* dummy port for function */ struct proc *sc_proc; /* task thread */ TAILQ_HEAD(,usbf_task) sc_tskq; /* task queue head */ int sc_dying; u_int8_t *sc_hs_config; }; #define DEVNAME(sc) ((sc)->sc_dev.dv_xname) int usbf_match(struct device *, void *, void *); void usbf_attach(struct device *, struct device *, void *); void usbf_create_thread(void *); void usbf_task_thread(void *); usbf_status usbf_get_descriptor(usbf_device_handle, usb_device_request_t *, void **); void usbf_set_address(usbf_device_handle, u_int8_t); usbf_status usbf_set_config(usbf_device_handle, u_int8_t); #ifdef USBF_DEBUG void usbf_dump_request(usbf_device_handle, usb_device_request_t *); #endif struct cfattach usbf_ca = { sizeof(struct usbf_softc), usbf_match, usbf_attach }; struct cfdriver usbf_cd = { NULL, "usbf", DV_DULL }; static const char * const usbrev_str[] = USBREV_STR; int usbf_match(struct device *parent, void *match, void *aux) { return UMATCH_GENERIC; } void usbf_attach(struct device *parent, struct device *self, void *aux) { struct usbf_softc *sc = (struct usbf_softc *)self; int usbrev; int speed; usbf_status err; /* Continue to set up the bus struct. */ sc->sc_bus = aux; sc->sc_bus->usbfctl = sc; usbrev = sc->sc_bus->usbrev; printf(": USB revision %s", usbrev_str[usbrev]); switch (usbrev) { case USBREV_2_0: speed = USB_SPEED_HIGH; break; case USBREV_1_1: case USBREV_1_0: speed = USB_SPEED_FULL; break; default: printf(", not supported\n"); sc->sc_dying = 1; return; } printf("\n"); /* Initialize the usbf struct. */ TAILQ_INIT(&sc->sc_tskq); /* Establish the software interrupt. */ if (usbf_softintr_establish(sc->sc_bus)) { printf("%s: can't establish softintr\n", DEVNAME(sc)); sc->sc_dying = 1; return; } /* Attach the function driver. */ err = usbf_new_device(self, sc->sc_bus, 0, speed, 0, &sc->sc_port); if (err) { printf("%s: usbf_new_device failed, %s\n", DEVNAME(sc), usbf_errstr(err)); sc->sc_dying = 1; return; } /* Create a process context for asynchronous tasks. */ config_pending_incr(); kthread_create_deferred(usbf_create_thread, sc); } /* * USB device tasks */ /* * Add a task to be performed by the task thread. This function can be * called from any context and the task function will be executed in a * process context ASAP. */ void usbf_add_task(usbf_device_handle dev, struct usbf_task *task) { struct usbf_softc *sc = dev->bus->usbfctl; int s; s = splusb(); if (!task->onqueue) { DPRINTF(1,("usbf_add_task: task=%p, proc=%s\n", task, sc->sc_bus->intr_context ? "(null)" : curproc->p_comm)); TAILQ_INSERT_TAIL(&sc->sc_tskq, task, next); task->onqueue = 1; } else { DPRINTF(0,("usbf_add_task: task=%p on q, proc=%s\n", task, sc->sc_bus->intr_context ? "(null)" : curproc->p_comm)); } wakeup(&sc->sc_tskq); splx(s); } void usbf_rem_task(usbf_device_handle dev, struct usbf_task *task) { struct usbf_softc *sc = dev->bus->usbfctl; int s; s = splusb(); if (task->onqueue) { DPRINTF(1,("usbf_rem_task: task=%p\n", task)); TAILQ_REMOVE(&sc->sc_tskq, task, next); task->onqueue = 0; } else { DPRINTF(0,("usbf_rem_task: task=%p not on q", task)); } splx(s); } /* * Called from the kernel proper when it can create threads. */ void usbf_create_thread(void *arg) { struct usbf_softc *sc = arg; if (kthread_create(usbf_task_thread, sc, &sc->sc_proc, "%s", DEVNAME(sc)) != 0) { printf("%s: can't create task thread\n", DEVNAME(sc)); return; } config_pending_decr(); } /* * Process context for USB function tasks. */ void usbf_task_thread(void *arg) { struct usbf_softc *sc = arg; struct usbf_task *task; int s; DPRINTF(0,("usbf_task_thread: start (pid %d)\n", curproc->p_pid)); s = splusb(); while (!sc->sc_dying) { task = TAILQ_FIRST(&sc->sc_tskq); if (task == NULL) { tsleep(&sc->sc_tskq, PWAIT, "usbtsk", 0); task = TAILQ_FIRST(&sc->sc_tskq); } DPRINTF(1,("usbf_task_thread: woke up task=%p\n", task)); if (task != NULL) { TAILQ_REMOVE(&sc->sc_tskq, task, next); task->onqueue = 0; splx(s); task->fun(task->arg); s = splusb(); DPRINTF(1,("usbf_task_thread: done task=%p\n", task)); } } splx(s); DPRINTF(0,("usbf_task_thread: exit\n")); kthread_exit(0); } /* * Bus event handling */ void usbf_host_reset(usbf_bus_handle bus) { usbf_device_handle dev = bus->usbfctl->sc_port.device; DPRINTF(0,("usbf_host_reset\n")); /* Change device state from any state backe to Default. */ (void)usbf_set_config(dev, USB_UNCONFIG_NO); dev->address = 0; } /* * Device request handling */ usbf_status usbf_get_descriptor(usbf_device_handle dev, usb_device_request_t *req, void **data) { u_int8_t type = UGETW(req->wValue) >> 8; u_int8_t index = UGETW(req->wValue) & 0xff; usb_device_descriptor_t *dd; usb_config_descriptor_t *cd; usb_string_descriptor_t *sd; struct usbf_softc *sc; switch (type) { case UDESC_DEVICE: dd = usbf_device_descriptor(dev); *data = dd; USETW(req->wLength, MIN(UGETW(req->wLength), dd->bLength));; return USBF_NORMAL_COMPLETION; case UDESC_DEVICE_QUALIFIER: { static usb_device_qualifier_t dq; dd = usbf_device_descriptor(dev); bzero(&dq, sizeof dq); dq.bLength = USB_DEVICE_QUALIFIER_SIZE; dq.bDescriptorType = UDESC_DEVICE_QUALIFIER; USETW(dq.bcdUSB, 0x0200); dq.bDeviceClass = dd->bDeviceClass; dq.bDeviceSubClass = dd->bDeviceSubClass; dq.bDeviceProtocol = dd->bDeviceProtocol; dq.bMaxPacketSize0 = dd->bMaxPacketSize; dq.bNumConfigurations = dd->bNumConfigurations; *data = &dq; USETW(req->wLength, MIN(UGETW(req->wLength), dq.bLength));; return USBF_NORMAL_COMPLETION; } case UDESC_CONFIG: cd = usbf_config_descriptor(dev, index); if (cd == NULL) return USBF_INVAL; *data = cd; USETW(req->wLength, MIN(UGETW(req->wLength), UGETW(cd->wTotalLength))); return USBF_NORMAL_COMPLETION; /* XXX */ case UDESC_OTHER_SPEED_CONFIGURATION: cd = usbf_config_descriptor(dev, index); if (cd == NULL) return USBF_INVAL; sc = dev->bus->usbfctl; if (sc->sc_hs_config == NULL) { /* XXX should allocate more dynamically */ sc->sc_hs_config = (u_int8_t *)malloc(65536, M_USB, M_NOWAIT); } if (sc->sc_hs_config == NULL) return USBF_INVAL; bcopy(cd, sc->sc_hs_config, UGETW(cd->wTotalLength)); *data = sc->sc_hs_config; ((usb_config_descriptor_t *)sc->sc_hs_config)->bDescriptorType = UDESC_OTHER_SPEED_CONFIGURATION; USETW(req->wLength, MIN(UGETW(req->wLength), UGETW(cd->wTotalLength))); return USBF_NORMAL_COMPLETION; case UDESC_STRING: sd = usbf_string_descriptor(dev, index); if (sd == NULL) return USBF_INVAL; *data = sd; USETW(req->wLength, MIN(UGETW(req->wLength), sd->bLength)); return USBF_NORMAL_COMPLETION; default: DPRINTF(0,("usbf_get_descriptor: unknown descriptor type=%u\n", type)); return USBF_INVAL; } } /* * Change device state from Default to Address, or change the device address * if the device is not currently in the Default state. */ void usbf_set_address(usbf_device_handle dev, u_int8_t address) { DPRINTF(0,("usbf_set_address: dev=%p, %u -> %u\n", dev, dev->address, address)); dev->address = address; } /* * If the device was in the Addressed state (dev->config == NULL) before, it * will be in the Configured state upon successful return from this routine. */ usbf_status usbf_set_config(usbf_device_handle dev, u_int8_t new) { usbf_config_handle cfg = dev->config; usbf_function_handle fun = dev->function; usbf_status err = USBF_NORMAL_COMPLETION; u_int8_t old = cfg ? cfg->uc_cdesc->bConfigurationValue : USB_UNCONFIG_NO; if (old == new) return USBF_NORMAL_COMPLETION; DPRINTF(0,("usbf_set_config: dev=%p, %u -> %u\n", dev, old, new)); /* * Resetting the device state to Unconfigured must always succeed. * This happens typically when the host resets the bus. */ if (new == USB_UNCONFIG_NO) { if (dev->function->methods->set_config) err = fun->methods->set_config(fun, NULL); if (err) { DPRINTF(0,("usbf_set_config: %s\n", usbf_errstr(err))); } dev->config = NULL; return USBF_NORMAL_COMPLETION; } /* * Changing the device configuration may fail. The function * may decline to set the new configuration. */ SIMPLEQ_FOREACH(cfg, &dev->configs, next) { if (cfg->uc_cdesc->bConfigurationValue == new) { if (dev->function->methods->set_config) err = fun->methods->set_config(fun, cfg); if (!err) dev->config = cfg; return err; } } return USBF_INVAL; } /* * Handle device requests coming in via endpoint 0 pipe. */ void usbf_do_request(usbf_xfer_handle xfer, usbf_private_handle priv, usbf_status err) { usbf_device_handle dev = xfer->pipe->device; usb_device_request_t *req = xfer->buffer; usbf_config_handle cfg; void *data = NULL; u_int16_t value; u_int16_t index; if (err) { DPRINTF(0,("usbf_do_request: receive failed, %s\n", usbf_errstr(err))); return; } #ifdef USBF_DEBUG if (usbfdebug >= 0) usbf_dump_request(dev, req); #endif #define C(x,y) ((x) | ((y) << 8)) switch (C(req->bRequest, req->bmRequestType)) { case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): /* Change device state from Default to Address. */ usbf_set_address(dev, UGETW(req->wValue)); break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): /* Change device state from Address to Configured. */ printf("set config activated\n"); err = usbf_set_config(dev, UGETW(req->wValue) & 0xff); break; case C(UR_GET_CONFIG, UT_READ_DEVICE): { /* XXX */ if ((cfg = dev->config) == NULL) { static u_int8_t zero = 0; data = &zero; } else data = &cfg->uc_cdesc->bConfigurationValue; USETW(req->wLength, MIN(UGETW(req->wLength), 1));; } break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): err = usbf_get_descriptor(dev, req, &data); break; case C(UR_GET_STATUS, UT_READ_DEVICE): DPRINTF(1,("usbf_do_request: UR_GET_STATUS %d\n", UGETW(req->wLength))); data = &dev->status; USETW(req->wLength, MIN(UGETW(req->wLength), sizeof dev->status)); break; case C(UR_GET_STATUS, UT_READ_ENDPOINT): { //u_int8_t addr = UGETW(req->wIndex) & 0xff; static u_int16_t status = 0; data = &status; USETW(req->wLength, MIN(UGETW(req->wLength), sizeof status)); break; } case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): value = UGETW(req->wValue); index = UGETW(req->wIndex); if ((cfg = dev->config) == NULL) err = USBF_STALLED; else err = usbf_set_endpoint_feature(cfg, index, value); break; case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): value = UGETW(req->wValue); index = UGETW(req->wIndex); if ((cfg = dev->config) == NULL) err = USBF_STALLED; else err = usbf_clear_endpoint_feature(cfg, index, value); break; /* Alternate settings for interfaces are unsupported. */ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): if (UGETW(req->wValue) != 0) err = USBF_STALLED; break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): { static u_int8_t zero = 0; data = &zero; USETW(req->wLength, MIN(UGETW(req->wLength), 1)); break; } default: { usbf_function_handle fun = dev->function; if (fun == NULL) err = USBF_STALLED; else /* XXX change prototype for this method to remove * XXX the data argument. */ err = fun->methods->do_request(fun, req, &data); } } if (err) { DPRINTF(0,("usbf_do_request: request=%#x, type=%#x " "failed, %s\n", req->bRequest, req->bmRequestType, usbf_errstr(err))); usbf_stall_pipe(dev->default_pipe); } else if (UGETW(req->wLength) > 0) { if (data == NULL) { DPRINTF(0,("usbf_do_request: no data, " "sending ZLP\n")); USETW(req->wLength, 0); } /* Transfer IN data in response to the request. */ usbf_setup_xfer(dev->data_xfer, dev->default_pipe, NULL, data, UGETW(req->wLength), 0, 0, NULL); err = usbf_transfer(dev->data_xfer); if (err && err != USBF_IN_PROGRESS) { DPRINTF(0,("usbf_do_request: data xfer=%p, %s\n", xfer, usbf_errstr(err))); } } /* Schedule another request transfer. */ usbf_setup_default_xfer(dev->default_xfer, dev->default_pipe, NULL, &dev->def_req, 0, 0, usbf_do_request); err = usbf_transfer(dev->default_xfer); if (err && err != USBF_IN_PROGRESS) { DPRINTF(0,("usbf_do_request: ctrl xfer=%p, %s\n", xfer, usbf_errstr(err))); } } #ifdef USBF_DEBUG struct usb_enum_str { int code; const char * const str; }; static const struct usb_enum_str usb_request_str[] = { { UR_GET_STATUS, "GET STATUS" }, { UR_CLEAR_FEATURE, "CLEAR FEATURE" }, { UR_SET_FEATURE, "SET FEATURE" }, { UR_SET_ADDRESS, "SET ADDRESS" }, { UR_GET_DESCRIPTOR, "GET DESCRIPTOR" }, { UR_SET_DESCRIPTOR, "SET DESCRIPTOR" }, { UR_GET_CONFIG, "GET CONFIG" }, { UR_SET_CONFIG, "SET CONFIG" }, { UR_GET_INTERFACE, "GET INTERFACE" }, { UR_SET_INTERFACE, "SET INTERFACE" }, { UR_SYNCH_FRAME, "SYNCH FRAME" }, { 0, NULL } }; static const struct usb_enum_str usb_request_type_str[] = { { UT_READ_DEVICE, "Read Device" }, { UT_READ_INTERFACE, "Read Interface" }, { UT_READ_ENDPOINT, "Read Endpoint" }, { UT_WRITE_DEVICE, "Write Device" }, { UT_WRITE_INTERFACE, "Write Interface" }, { UT_WRITE_ENDPOINT, "Write Endpoint" }, { UT_READ_CLASS_DEVICE, "Read Class Device" }, { UT_READ_CLASS_INTERFACE, "Read Class Interface" }, { UT_READ_CLASS_OTHER, "Read Class Other" }, { UT_READ_CLASS_ENDPOINT, "Read Class Endpoint" }, { UT_WRITE_CLASS_DEVICE, "Write Class Device" }, { UT_WRITE_CLASS_INTERFACE, "Write Class Interface" }, { UT_WRITE_CLASS_OTHER, "Write Class Other" }, { UT_WRITE_CLASS_ENDPOINT, "Write Class Endpoint" }, { UT_READ_VENDOR_DEVICE, "Read Vendor Device" }, { UT_READ_VENDOR_INTERFACE, "Read Vendor Interface" }, { UT_READ_VENDOR_OTHER, "Read Vendor Other" }, { UT_READ_VENDOR_ENDPOINT, "Read Vendor Endpoint" }, { UT_WRITE_VENDOR_DEVICE, "Write Vendor Device" }, { UT_WRITE_VENDOR_INTERFACE, "Write Vendor Interface" }, { UT_WRITE_VENDOR_OTHER, "Write Vendor Other" }, { UT_WRITE_VENDOR_ENDPOINT, "Write Vendor Endpoint" }, { 0, NULL } }; static const struct usb_enum_str usb_request_desc_str[] = { { UDESC_DEVICE, "Device" }, { UDESC_CONFIG, "Configuration" }, { UDESC_STRING, "String" }, { UDESC_INTERFACE, "Interface" }, { UDESC_ENDPOINT, "Endpoint" }, { UDESC_DEVICE_QUALIFIER, "Device Qualifier" }, { UDESC_OTHER_SPEED_CONFIGURATION, "Other Speed Configuration" }, { UDESC_INTERFACE_POWER, "Interface Power" }, { UDESC_OTG, "OTG" }, { UDESC_CS_DEVICE, "Class-specific Device" }, { UDESC_CS_CONFIG, "Class-specific Configuration" }, { UDESC_CS_STRING, "Class-specific String" }, { UDESC_CS_INTERFACE, "Class-specific Interface" }, { UDESC_CS_ENDPOINT, "Class-specific Endpoint" }, { UDESC_HUB, "Hub" }, { 0, NULL } }; static const char * usb_enum_string(const struct usb_enum_str *tab, int code) { static char buf[16]; while (tab->str != NULL) { if (tab->code == code) return tab->str; tab++; } (void)snprintf(buf, sizeof buf, "0x%02x", code); return buf; } static const char * usbf_request_code_string(usb_device_request_t *req) { static char buf[32]; (void)snprintf(buf, sizeof buf, "%s", usb_enum_string(usb_request_str, req->bRequest)); return buf; } static const char * usbf_request_type_string(usb_device_request_t *req) { static char buf[32]; (void)snprintf(buf, sizeof buf, "%s", usb_enum_string(usb_request_type_str, req->bmRequestType)); return buf; } static const char * usbf_request_desc_string(usb_device_request_t *req) { static char buf[32]; u_int8_t type = UGETW(req->wValue) >> 8; u_int8_t index = UGETW(req->wValue) & 0xff; (void)snprintf(buf, sizeof buf, "%s/%u", usb_enum_string(usb_request_desc_str, type), index); return buf; } void usbf_dump_request(usbf_device_handle dev, usb_device_request_t *req) { struct usbf_softc *sc = dev->bus->usbfctl; printf("%s: %s request %s\n", DEVNAME(sc), usbf_request_type_string(req), usbf_request_code_string(req)); if (req->bRequest == UR_GET_DESCRIPTOR) printf("%s: VALUE: 0x%04x (%s)\n", DEVNAME(sc), UGETW(req->wValue), usbf_request_desc_string(req)); else printf("%s: VALUE: 0x%04x\n", DEVNAME(sc), UGETW(req->wValue)); printf("%s: INDEX: 0x%04x\n", DEVNAME(sc), UGETW(req->wIndex)); printf("%s: LENGTH: 0x%04x\n", DEVNAME(sc), UGETW(req->wLength)); } #endif