diff options
-rw-r--r-- | sys/arch/arm/xscale/files.pxa2x0 | 4 | ||||
-rw-r--r-- | sys/arch/arm/xscale/pxa27x_udc.c | 817 | ||||
-rw-r--r-- | sys/arch/arm/xscale/pxa27x_udcreg.h | 21 | ||||
-rw-r--r-- | sys/arch/zaurus/conf/GENERIC | 6 | ||||
-rw-r--r-- | sys/arch/zaurus/include/zaurus_reg.h | 6 | ||||
-rw-r--r-- | sys/conf/files | 3 | ||||
-rw-r--r-- | sys/dev/usb/files.usb | 17 | ||||
-rw-r--r-- | sys/dev/usb/if_cdcef.c | 252 | ||||
-rw-r--r-- | sys/dev/usb/usbf.c | 698 | ||||
-rw-r--r-- | sys/dev/usb/usbf_subr.c | 1099 | ||||
-rw-r--r-- | sys/dev/usb/usbfvar.h | 175 |
11 files changed, 3046 insertions, 52 deletions
diff --git a/sys/arch/arm/xscale/files.pxa2x0 b/sys/arch/arm/xscale/files.pxa2x0 index d37eb8edc0a..ec2b493eb36 100644 --- a/sys/arch/arm/xscale/files.pxa2x0 +++ b/sys/arch/arm/xscale/files.pxa2x0 @@ -1,4 +1,4 @@ -# $OpenBSD: files.pxa2x0,v 1.18 2005/12/20 02:37:09 drahn Exp $ +# $OpenBSD: files.pxa2x0,v 1.19 2006/11/25 18:10:29 uwe Exp $ # $NetBSD: files.pxa2x0,v 1.6 2004/05/01 19:09:14 thorpej Exp $ # # Configuration info for Intel PXA2[51]0 CPU support @@ -46,7 +46,7 @@ file arch/arm/xscale/pxa2x0_a4x_space.c com_pxaip file arch/arm/xscale/pxa2x0_a4x_io.S com_pxaip # PXA27x USB Device Controller -device pxaudc +device pxaudc: usbdev attach pxaudc at pxaip file arch/arm/xscale/pxa27x_udc.c pxaudc diff --git a/sys/arch/arm/xscale/pxa27x_udc.c b/sys/arch/arm/xscale/pxa27x_udc.c index 94f843422e2..e802860af51 100644 --- a/sys/arch/arm/xscale/pxa27x_udc.c +++ b/sys/arch/arm/xscale/pxa27x_udc.c @@ -1,7 +1,8 @@ -/* $OpenBSD: pxa27x_udc.c,v 1.5 2005/03/30 14:24:39 dlg Exp $ */ +/* $OpenBSD: pxa27x_udc.c,v 1.6 2006/11/25 18:10:29 uwe Exp $ */ /* * Copyright (c) 2005 David Gwynne <dlg@openbsd.org> + * 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 @@ -20,31 +21,98 @@ #include <sys/systm.h> #include <sys/device.h> #include <sys/kernel.h> +#include <sys/malloc.h> #include <machine/intr.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> + #include <arm/xscale/pxa2x0reg.h> #include <arm/xscale/pxa2x0var.h> #include <arm/xscale/pxa2x0_gpio.h> #include <arm/xscale/pxa27x_udcreg.h> +#define PXAUDC_EP0MAXP 16 /* XXX */ +#define PXAUDC_NEP 24 /* total number of endpoints */ -int pxaudc_match(struct device *, void *, void *); -void pxaudc_attach(struct device *, struct device *, void *); -int pxaudc_detach(struct device *, int); -void pxaudc_power(int, void *); +#include <machine/zaurus_reg.h> /* XXX */ -struct pxaudc_softc { - struct device sc_dev; - bus_space_tag_t sc_iot; - bus_space_handle_t sc_ioh; - bus_size_t sc_size; +struct pxaudc_xfer { + struct usbf_xfer xfer; + u_int16_t frmlen; +}; +struct pxaudc_pipe { + struct usbf_pipe pipe; + LIST_ENTRY(pxaudc_pipe) list; +}; + +struct pxaudc_softc { + struct usbf_bus sc_bus; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + bus_size_t sc_size; + void *sc_ih; + void *sc_conn_ih; void *sc_powerhook; + SIMPLEQ_HEAD(,usbf_xfer) sc_free_xfers; /* recycled xfers */ + u_int32_t sc_icr0; /* enabled EP interrupts */ + u_int32_t sc_icr1; /* enabled EP interrupts */ + enum { + EP0_SETUP, + EP0_IN + } sc_ep0state; + u_int32_t sc_isr0; /* XXX deferred interrupts */ + u_int32_t sc_isr1; /* XXX deferred interrupts */ + u_int32_t sc_otgisr; /* XXX deferred interrupts */ + struct pxaudc_pipe *sc_pipe[PXAUDC_NEP]; }; -void pxaudc_enable(struct pxaudc_softc *); +int pxaudc_match(struct device *, void *, void *); +void pxaudc_attach(struct device *, struct device *, void *); +int pxaudc_detach(struct device *, int); +void pxaudc_power(int, void *); + +int pxaudc_is_host(void); +int pxaudc_is_device(void); +void pxaudc_setup(struct pxaudc_softc *); +void pxaudc_hide(struct pxaudc_softc *); +void pxaudc_show(struct pxaudc_softc *); + +void pxaudc_enable(struct pxaudc_softc *); +void pxaudc_disable(struct pxaudc_softc *); +void pxaudc_read_ep0(struct pxaudc_softc *, usbf_xfer_handle); +void pxaudc_write_ep0(struct pxaudc_softc *, usbf_xfer_handle); +void pxaudc_write(struct pxaudc_softc *, usbf_xfer_handle); + +int pxaudc_connect_intr(void *); +int pxaudc_intr(void *); +void pxaudc_intr1(struct pxaudc_softc *); +void pxaudc_ep0_intr(struct pxaudc_softc *); + +usbf_status pxaudc_open(struct usbf_pipe *); +void pxaudc_softintr(void *); +usbf_status pxaudc_allocm(struct usbf_bus *, usb_dma_t *, u_int32_t); +void pxaudc_freem(struct usbf_bus *, usb_dma_t *); +usbf_xfer_handle pxaudc_allocx(struct usbf_bus *); +void pxaudc_freex(struct usbf_bus *, usbf_xfer_handle); + +usbf_status pxaudc_ctrl_transfer(usbf_xfer_handle); +usbf_status pxaudc_ctrl_start(usbf_xfer_handle); +void pxaudc_ctrl_abort(usbf_xfer_handle); +void pxaudc_ctrl_done(usbf_xfer_handle); +void pxaudc_ctrl_close(usbf_pipe_handle); + +usbf_status pxaudc_bulk_transfer(usbf_xfer_handle); +usbf_status pxaudc_bulk_start(usbf_xfer_handle); +void pxaudc_bulk_abort(usbf_xfer_handle); +void pxaudc_bulk_done(usbf_xfer_handle); +void pxaudc_bulk_close(usbf_pipe_handle); struct cfattach pxaudc_ca = { sizeof(struct pxaudc_softc), pxaudc_match, pxaudc_attach, @@ -55,6 +123,52 @@ struct cfdriver pxaudc_cd = { NULL, "pxaudc", DV_DULL }; +struct usbf_bus_methods pxaudc_bus_methods = { + pxaudc_open, + pxaudc_softintr, + pxaudc_allocm, + pxaudc_freem, + pxaudc_allocx, + pxaudc_freex +}; + +struct usbf_pipe_methods pxaudc_ctrl_methods = { + pxaudc_ctrl_transfer, + pxaudc_ctrl_start, + pxaudc_ctrl_abort, + pxaudc_ctrl_done, + pxaudc_ctrl_close +}; + +struct usbf_pipe_methods pxaudc_bulk_methods = { + pxaudc_bulk_transfer, + pxaudc_bulk_start, + pxaudc_bulk_abort, + pxaudc_bulk_done, + pxaudc_bulk_close +}; + +#define DEVNAME(sc) USBDEVNAME((sc)->sc_bus.bdev) + +#define CSR_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) +#define CSR_WRITE_4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define CSR_WRITE_1(sc, reg, val) \ + bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define CSR_SET_4(sc, reg, val) \ + CSR_WRITE_4((sc), (reg), CSR_READ_4((sc), (reg)) | (val)) +#define CSR_CLR_4(sc, reg, val) \ + CSR_WRITE_4((sc), (reg), CSR_READ_4((sc), (reg)) & ~(val)) + +#define PXAUDC_DEBUG +#ifndef PXAUDC_DEBUG +#define DPRINTF(l, x) do {} while (0) +#else +int pxaudcdebug = 0; +#define DPRINTF(l, x) if ((l) <= pxaudcdebug) printf x; else {} +#endif + int pxaudc_match(struct device *parent, void *match, void *aux) { @@ -71,9 +185,6 @@ pxaudc_attach(struct device *parent, struct device *self, void *aux) struct pxaip_attach_args *pxa = aux; sc->sc_iot = pxa->pxa_iot; - sc->sc_size = 0; - sc->sc_powerhook = NULL; - if (bus_space_map(sc->sc_iot, PXA2X0_USBDC_BASE, PXA2X0_USBDC_SIZE, 0, &sc->sc_ioh)) { printf(": cannot map mem space\n"); @@ -86,18 +197,51 @@ pxaudc_attach(struct device *parent, struct device *self, void *aux) bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, sc->sc_size, BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE); - pxa2x0_gpio_set_function(35, GPIO_ALT_FN_2_IN); /* USB_P2_1 */ - pxa2x0_gpio_set_function(37, GPIO_ALT_FN_1_OUT); /* USB_P2_8 */ - pxa2x0_gpio_set_function(41, GPIO_ALT_FN_2_IN); /* USB_P2_7 */ - pxa2x0_gpio_set_function(89, GPIO_ALT_FN_2_OUT); /* USBHPEN<1> */ - pxa2x0_gpio_set_function(120, GPIO_ALT_FN_2_OUT); /* USBHPEN<2> */ + /* Set up GPIO pins and disable the controller. */ + pxaudc_setup(sc); + pxaudc_disable(sc); - pxa2x0_clkman_config(CKEN_USBDC, 0); - pxaudc_enable(sc); + /* Establish USB device interrupt. */ + sc->sc_ih = pxa2x0_intr_establish(PXA2X0_INT_USB, IPL_USB, + pxaudc_intr, sc, DEVNAME(sc)); + if (sc->sc_ih == NULL) { + printf(": unable to establish interrupt\n"); + bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size); + sc->sc_size = 0; + return; + } - pxa2x0_gpio_set_bit(37); /* USB_P2_8 */ + /* Establish device connect interrupt. */ +#if 0 + sc->sc_conn_ih = pxa2x0_gpio_intr_establish(C3000_USB_DEVICE_PIN, /* XXX */ + IST_EDGE_BOTH, IPL_USB, pxaudc_connect_intr, sc, "usbc"); +#endif + sc->sc_conn_ih = pxa2x0_gpio_intr_establish(C3000_USB_CONNECT_PIN, + IST_EDGE_BOTH, IPL_USB, pxaudc_connect_intr, sc, "usbc"); + if (sc->sc_conn_ih == NULL) { + printf(": unable to establish connect interrupt\n"); + pxa2x0_intr_disestablish(sc->sc_ih); + bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size); + sc->sc_ioh = NULL; + sc->sc_size = 0; + return; + } sc->sc_powerhook = powerhook_establish(pxaudc_power, sc); + + /* Set up the bus struct. */ + sc->sc_bus.methods = &pxaudc_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct pxaudc_pipe); + sc->sc_bus.ep0_maxp = PXAUDC_EP0MAXP; + sc->sc_bus.usbrev = USBREV_1_1; + sc->sc_bus.dmatag = pxa->pxa_dmat; + + /* Attach logical device and function. */ + (void)config_found(self, &sc->sc_bus, NULL); + + /* Enable the controller unless we're now acting as a host. */ + if (!pxaudc_is_host()) + pxaudc_enable(sc); } int @@ -108,6 +252,12 @@ pxaudc_detach(struct device *self, int flags) if (sc->sc_powerhook != NULL) powerhook_disestablish(sc->sc_powerhook); + if (sc->sc_conn_ih != NULL) + pxa2x0_gpio_intr_disestablish(sc->sc_conn_ih); + + if (sc->sc_ih != NULL) + pxa2x0_intr_disestablish(sc->sc_ih); + if (sc->sc_size) { bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size); sc->sc_size = 0; @@ -121,32 +271,611 @@ pxaudc_power(int why, void *arg) { struct pxaudc_softc *sc = (struct pxaudc_softc *)arg; - if (why == PWR_RESUME) + switch (why) { + case PWR_SUSPEND: + case PWR_STANDBY: + pxaudc_disable(sc); + break; + + case PWR_RESUME: pxaudc_enable(sc); + break; + } +} + +/* + * Machine-specific functions + */ + +/* XXX move to machine-specific file */ + +int +pxaudc_is_host(void) +{ + return (!pxa2x0_gpio_get_bit(C3000_USB_CONNECT_PIN) && + !pxa2x0_gpio_get_bit(C3000_USB_DEVICE_PIN)); +} + +int +pxaudc_is_device(void) +{ + return (pxa2x0_gpio_get_bit(C3000_USB_CONNECT_PIN) && + pxa2x0_gpio_get_bit(C3000_USB_DEVICE_PIN)); +} + +void +pxaudc_setup(struct pxaudc_softc *sc) +{ + pxa2x0_gpio_set_function(45, GPIO_OUT); + pxa2x0_gpio_set_function(C3000_USB_CONNECT_PIN, GPIO_IN); /* 41 */ + pxa2x0_gpio_set_function(40, GPIO_OUT); + pxa2x0_gpio_set_function(39, GPIO_IN); + pxa2x0_gpio_set_function(38, GPIO_IN); + pxa2x0_gpio_set_function(37, GPIO_OUT); + pxa2x0_gpio_set_function(36, GPIO_IN); + pxa2x0_gpio_set_function(C3000_USB_DEVICE_PIN, GPIO_IN); /* 35 */ + pxa2x0_gpio_set_function(34, GPIO_IN); + pxa2x0_gpio_set_function(89, GPIO_OUT); + pxa2x0_gpio_set_function(120, GPIO_OUT); +} + +/* Hide us from the host. */ +void +pxaudc_hide(struct pxaudc_softc *sc) +{ + pxa2x0_gpio_clear_bit(C3000_USB_PULLUP_PIN); +} + +/* Show us to the host. */ +void +pxaudc_show(struct pxaudc_softc *sc) +{ + pxa2x0_gpio_set_bit(C3000_USB_PULLUP_PIN); +} + +/* + * Register manipulation + */ + +#if 0 +static void +pxaudc_dump_regs(struct pxaudc_softc *sc) +{ + printf("UDCCR\t%b\n", CSR_READ_4(sc, USBDC_UDCCR), + USBDC_UDCCR_BITS); + printf("UDCICR0\t%b\n", CSR_READ_4(sc, USBDC_UDCICR0), + USBDC_UDCISR0_BITS); + printf("UDCICR1\t%b\n", CSR_READ_4(sc, USBDC_UDCICR1), + USBDC_UDCISR1_BITS); + printf("OTGICR\t%b\n", CSR_READ_4(sc, USBDC_UDCOTGICR), + USBDC_UDCOTGISR_BITS); } +#endif void pxaudc_enable(struct pxaudc_softc *sc) { - u_int32_t hr; - - /* disable the controller */ - hr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, USBDC_UDCCR); - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UDCCR, - hr & ~USBDC_UDCCR_UDE); - - hr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, USBDC_UDCICR1); - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UDCICR1, - hr | USBDC_UDCICR1_IERS); - - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR, 0); - hr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR); - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR, - hr | USBDC_UP2OCR_HXS); - hr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR); - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR, - hr | USBDC_UP2OCR_HXOE); - hr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR); - bus_space_write_4(sc->sc_iot, sc->sc_ioh, USBDC_UP2OCR, - hr | USBDC_UP2OCR_DPPDE|USBDC_UP2OCR_DMPDE); + DPRINTF(0,("pxaudc_enable\n")); + + /* Start the clocks. */ + pxa2x0_clkman_config(CKEN_USBDC, 1); + + /* Configure Port 2 for USB device. */ + CSR_WRITE_4(sc, USBDC_UP2OCR, USBDC_UP2OCR_DMPUE | + USBDC_UP2OCR_DPPUE | USBDC_UP2OCR_HXOE); + + /* Enable interrupts for configured endpoints. */ + CSR_SET_4(sc, USBDC_UDCICR0, USBDC_UDCICR0_IE(0) | + sc->sc_icr0); + CSR_SET_4(sc, USBDC_UDCICR1, USBDC_UDCICR1_IERS | + USBDC_UDCICR1_IECC | sc->sc_icr1); + + /* XXX */ + CSR_WRITE_4(sc, USBDC_UDCICR0, 0x00000C3F); + CSR_WRITE_4(sc, USBDC_UDCECR(1), 0x0200D103); + CSR_WRITE_4(sc, USBDC_UDCECR(2), 0x02014103); + CSR_WRITE_4(sc, USBDC_UDCECR(3), 0x0201B403); + CSR_WRITE_4(sc, USBDC_UDCECR(4), 0x02022403); + CSR_WRITE_4(sc, USBDC_UDCECR(5), 0x0202F021); + + /* Enable the controller. */ + CSR_CLR_4(sc, USBDC_UDCCR, USBDC_UDCCR_EMCE); + CSR_SET_4(sc, USBDC_UDCCR, USBDC_UDCCR_UDE); + + /* Enable USB client on port 2. */ + pxa2x0_gpio_clear_bit(37); /* USB_P2_8 */ +} + +void +pxaudc_disable(struct pxaudc_softc *sc) +{ + DPRINTF(0,("pxaudc_disable\n")); + + /* Disable the controller. */ + CSR_CLR_4(sc, USBDC_UDCCR, USBDC_UDCCR_UDE); + + /* Disable all interrupts. */ + CSR_WRITE_4(sc, USBDC_UDCICR0, 0); + CSR_WRITE_4(sc, USBDC_UDCICR1, 0); + CSR_WRITE_4(sc, USBDC_UDCOTGICR, 0); + + /* Set Port 2 output to "Non-OTG Host with Differential Port". */ + CSR_WRITE_4(sc, USBDC_UP2OCR, USBDC_UP2OCR_HXS | + USBDC_UP2OCR_HXOE); + + /* Set "Host Port 2 Transceiver D Pull Down Enable". */ + CSR_SET_4(sc, USBDC_UP2OCR, USBDC_UP2OCR_DMPDE); + + /* Stop the clocks. */ + pxa2x0_clkman_config(CKEN_USBDC, 0); + + /* Enable USB host on port 2. */ + pxa2x0_gpio_set_bit(37); /* USB_P2_8 */ +} + +/* + * Endpoint FIFO handling + */ + +void +pxaudc_read_ep0(struct pxaudc_softc *sc, usbf_xfer_handle xfer) +{ + size_t len; + u_int8_t *p; + + xfer->actlen = CSR_READ_4(sc, USBDC_UDCBCR(0)); + len = MIN(xfer->actlen, xfer->length); + p = xfer->buffer; + + while (CSR_READ_4(sc, USBDC_UDCCSR0) & USBDC_UDCCSR0_RNE) { + u_int32_t v = CSR_READ_4(sc, USBDC_UDCDR(0)); + + if (len > 0) { + if (((unsigned)p & 0x3) == 0) + *(u_int32_t *)p = v; + else { + *(p+0) = v & 0xff; + *(p+1) = (v >> 8) & 0xff; + *(p+2) = (v >> 16) & 0xff; + *(p+3) = (v >> 24) & 0xff; + } + p += 4; + len -= 4; + } + } + + CSR_WRITE_4(sc, USBDC_UDCCSR0, USBDC_UDCCSR0_SA | USBDC_UDCCSR0_OPC); + + xfer->status = USBF_NORMAL_COMPLETION; + usbf_transfer_complete(xfer); +} + +void +pxaudc_write_ep0(struct pxaudc_softc *sc, usbf_xfer_handle xfer) +{ + struct pxaudc_xfer *lxfer = (struct pxaudc_xfer *)xfer; + u_int32_t len; + u_int8_t *p; + + if (lxfer->frmlen > 0) { + xfer->actlen += lxfer->frmlen; + lxfer->frmlen = 0; + } + + DPRINTF(1,("%s: ep0 ctrl-in, xfer=%p, len=%u, actlen=%u\n", + DEVNAME(sc), xfer, xfer->length, xfer->actlen)); + + if (xfer->actlen >= xfer->length) { + sc->sc_ep0state = EP0_SETUP; + usbf_transfer_complete(xfer); + return; + } + + sc->sc_ep0state = EP0_IN; + + p = (u_char *)xfer->buffer + xfer->actlen; + len = xfer->length - xfer->actlen; + len = MIN(len, PXAUDC_EP0MAXP); + lxfer->frmlen = len; + + while (len >= 4) { + u_int32_t v; + + if (((unsigned)p & 0x3) == 0) + v = *(u_int32_t *)p; + else { + v = *(p+0); + v |= *(p+1) << 8; + v |= *(p+2) << 16; + v |= *(p+3) << 24; + } + + CSR_WRITE_4(sc, USBDC_UDCDR(0), v); + len -= 4; + p += 4; + } + + while (len > 0) { + CSR_WRITE_1(sc, USBDC_UDCDR(0), *p); + len--; + p++; + } + + /* (12.6.7) Set IPR only for short packets. */ + if (lxfer->frmlen < PXAUDC_EP0MAXP) + CSR_SET_4(sc, USBDC_UDCCSR0, USBDC_UDCCSR0_IPR); +} + +void +pxaudc_write(struct pxaudc_softc *sc, usbf_xfer_handle xfer) +{ + printf("pxaudc_write: XXX\n"); +} + +/* + * Interrupt handling + */ + +int +pxaudc_connect_intr(void *v) +{ + struct pxaudc_softc *sc = v; + + DPRINTF(0,("pxaudc_connect_intr: connect=%d device=%d\n", + pxa2x0_gpio_get_bit(C3000_USB_CONNECT_PIN), + pxa2x0_gpio_get_bit(C3000_USB_DEVICE_PIN))); + + /* XXX only set a flag here */ + if (pxaudc_is_host()) { + pxaudc_disable(sc); + } else { + pxaudc_enable(sc); + } + + /* Claim this interrupt. */ + return 1; +} + +int +pxaudc_intr(void *v) +{ + struct pxaudc_softc *sc = v; + u_int32_t isr0, isr1, otgisr; + + isr0 = CSR_READ_4(sc, USBDC_UDCISR0); + isr1 = CSR_READ_4(sc, USBDC_UDCISR1); + otgisr = CSR_READ_4(sc, USBDC_UDCOTGISR); + + DPRINTF(1,("pxaudc_intr: isr0=%b, isr1=%b, otgisr=%b\n", + isr0, USBDC_UDCISR0_BITS, isr1, USBDC_UDCISR1_BITS, + otgisr, USBDC_UDCOTGISR_BITS)); + + if (isr0 || isr1 || otgisr) { + sc->sc_isr0 |= isr0; + sc->sc_isr1 |= isr1; + sc->sc_otgisr |= otgisr; + + //usbf_schedsoftintr(&sc->sc_bus); + pxaudc_intr1(sc); /* XXX */ + } + + CSR_WRITE_4(sc, USBDC_UDCISR0, isr0); + CSR_WRITE_4(sc, USBDC_UDCISR1, isr1); + CSR_WRITE_4(sc, USBDC_UDCOTGISR, otgisr); + + /* Claim this interrupt. */ + return 1; +} + +void +pxaudc_intr1(struct pxaudc_softc *sc) +{ + u_int32_t isr0, isr1, otgisr; + //int s; + + //s = splhardusb(); + isr0 = sc->sc_isr0; + isr1 = sc->sc_isr1; + otgisr = sc->sc_otgisr; + sc->sc_isr0 = 0; + sc->sc_isr1 = 0; + sc->sc_otgisr = 0; + //splx(s); + + sc->sc_bus.intr_context++; + + /* Handle USB RESET condition. */ + if (isr1 & USBDC_UDCISR1_IRRS) { + sc->sc_ep0state = EP0_SETUP; + usbf_host_reset(&sc->sc_bus); + /* Discard all other interrupts. */ + goto ret; + } + + /* Service control pipe interrupts. */ + if (isr0 & USBDC_UDCISR0_IR(0)) + pxaudc_ep0_intr(sc); + +ret: + sc->sc_bus.intr_context--; +} + +void +pxaudc_ep0_intr(struct pxaudc_softc *sc) +{ + struct pxaudc_pipe *ppipe; + usbf_pipe_handle pipe = NULL; + usbf_xfer_handle xfer = NULL; + u_int32_t csr0; + + csr0 = CSR_READ_4(sc, USBDC_UDCCSR0); + DPRINTF(1,("pxaudc_ep0_intr: csr0=%b\n", csr0, USBDC_UDCCSR0_BITS)); + + ppipe = sc->sc_pipe[0]; + if (ppipe != NULL) { + pipe = &ppipe->pipe; + xfer = SIMPLEQ_FIRST(&pipe->queue); + } + + if (sc->sc_ep0state == EP0_SETUP && (csr0 & USBDC_UDCCSR0_OPC)) { + if (pipe == NULL) { + DPRINTF(0,("pxaudc_ep0_intr: no control pipe\n")); + return; + } + + if (xfer == NULL) { + DPRINTF(0,("pxaudc_ep0_intr: no xfer\n")); + return; + } + + pxaudc_read_ep0(sc, xfer); + } else if (sc->sc_ep0state == EP0_IN && + (csr0 & USBDC_UDCCSR0_IPR) == 0 && xfer) { + pxaudc_write_ep0(sc, xfer); + } +} + +/* + * Bus methods + */ + +usbf_status +pxaudc_open(struct usbf_pipe *pipe) +{ + struct pxaudc_softc *sc = (struct pxaudc_softc *)pipe->device->bus; + struct pxaudc_pipe *ppipe = (struct pxaudc_pipe *)pipe; + int s; + + if (usbf_endpoint_index(pipe->endpoint) >= PXAUDC_NEP) + return USBF_BAD_ADDRESS; + + DPRINTF(1,("pxaudc_open\n")); + s = splhardusb(); + + switch (usbf_endpoint_type(pipe->endpoint)) { + case UE_CONTROL: + pipe->methods = &pxaudc_ctrl_methods; + break; + + case UE_BULK: + pipe->methods = &pxaudc_bulk_methods; + break; + + case UE_ISOCHRONOUS: + case UE_INTERRUPT: + default: + /* XXX */ + splx(s); + return USBF_BAD_ADDRESS; + } + + sc->sc_pipe[usbf_endpoint_index(pipe->endpoint)] = ppipe; + + splx(s); + return USBF_NORMAL_COMPLETION; +} + +void +pxaudc_softintr(void *v) +{ + struct pxaudc_softc *sc = v; + + pxaudc_intr1(sc); +} + +usbf_status +pxaudc_allocm(struct usbf_bus *bus, usb_dma_t *dmap, u_int32_t size) +{ + return usbf_allocmem(bus, size, 0, dmap); +} + +void +pxaudc_freem(struct usbf_bus *bus, usb_dma_t *dmap) +{ + usbf_freemem(bus, dmap); +} + +usbf_xfer_handle +pxaudc_allocx(struct usbf_bus *bus) +{ + struct pxaudc_softc *sc = (struct pxaudc_softc *)bus; + usbf_xfer_handle xfer; + + xfer = SIMPLEQ_FIRST(&sc->sc_free_xfers); + if (xfer != NULL) + SIMPLEQ_REMOVE_HEAD(&sc->sc_free_xfers, next); + else + xfer = malloc(sizeof(struct pxaudc_xfer), M_USB, M_NOWAIT); + if (xfer != NULL) + bzero(xfer, sizeof(struct pxaudc_xfer)); + return xfer; +} + +void +pxaudc_freex(struct usbf_bus *bus, usbf_xfer_handle xfer) +{ + struct pxaudc_softc *sc = (struct pxaudc_softc *)bus; + + SIMPLEQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +/* + * Control pipe methods + */ + +usbf_status +pxaudc_ctrl_transfer(usbf_xfer_handle xfer) +{ + usbf_status err; + + /* Insert last in queue. */ + err = usbf_insert_transfer(xfer); + if (err) + return err; + + /* + * Pipe isn't running (otherwise err would be USBF_IN_PROGRESS), + * so start first. + */ + return pxaudc_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); +} + +usbf_status +pxaudc_ctrl_start(usbf_xfer_handle xfer) +{ + struct usbf_pipe *pipe = xfer->pipe; + struct pxaudc_softc *sc = (struct pxaudc_softc *)pipe->device->bus; + int iswrite = !(xfer->rqflags & URQ_REQUEST); + int s; + + s = splusb(); + xfer->status = USBF_IN_PROGRESS; + if (iswrite) + pxaudc_write_ep0(sc, xfer); + else { + /* XXX boring message, this case is normally reached if + * XXX the xfer for a device request is being queued. */ + DPRINTF(0,("%s: ep0 ctrl-out, xfer=%p, len=%u, " + "actlen=%u\n", DEVNAME(sc), xfer, xfer->length, + xfer->actlen)); + } + splx(s); + return USBF_IN_PROGRESS; +} + +/* (also used by bulk pipes) */ +void +pxaudc_ctrl_abort(usbf_xfer_handle xfer) +{ + struct usbf_pipe *pipe = xfer->pipe; + struct pxaudc_softc *sc = (struct pxaudc_softc *)pipe->device->bus; + int s; +#ifdef PXAUDC_DEBUG + int index = usbf_endpoint_index(pipe->endpoint); + int dir = usbf_endpoint_dir(pipe->endpoint); + int type = usbf_endpoint_type(pipe->endpoint); +#endif + + DPRINTF(0,("%s: ep%d %s-%s abort, xfer=%p\n", DEVNAME(sc), index, + type == UE_CONTROL ? "ctrl" : "bulk", dir == UE_DIR_IN ? + "in" : "out", xfer)); + + /* + * Step 1: Make soft interrupt routine and hardware ignore the xfer. + */ + s = splusb(); + xfer->status = USBF_CANCELLED; + usb_uncallout(xfer->timeout_handle, pxaudc_timeout, NULL); + splx(s); + + /* + * Step 2: Make sure hardware has finished any possible use of the + * xfer and the soft interrupt routine has run. + */ + s = splusb(); + /* XXX this does not seem right, what if there + * XXX are two xfers in the FIFO and we only want to + * XXX ignore one? */ +#ifdef notyet + pxaudc_flush(sc, usbf_endpoint_address(pipe->endpoint)); +#endif + /* XXX we're not doing DMA and the soft interrupt routine does not + XXX need to clean up anything. */ + splx(s); + + /* + * Step 3: Execute callback. + */ + s = splusb(); + usbf_transfer_complete(xfer); + splx(s); +} + +void +pxaudc_ctrl_done(usbf_xfer_handle xfer) +{ +} + +void +pxaudc_ctrl_close(usbf_pipe_handle pipe) +{ + /* XXX */ +} + +/* + * Bulk pipe methods + */ + +usbf_status +pxaudc_bulk_transfer(usbf_xfer_handle xfer) +{ + usbf_status err; + + /* Insert last in queue. */ + err = usbf_insert_transfer(xfer); + if (err) + return err; + + /* + * Pipe isn't running (otherwise err would be USBF_IN_PROGRESS), + * so start first. + */ + return pxaudc_bulk_start(SIMPLEQ_FIRST(&xfer->pipe->queue)); +} + +usbf_status +pxaudc_bulk_start(usbf_xfer_handle xfer) +{ + struct usbf_pipe *pipe = xfer->pipe; + struct pxaudc_softc *sc = (struct pxaudc_softc *)pipe->device->bus; + int iswrite = usbf_endpoint_dir(pipe->endpoint) == UE_DIR_IN; + int s; + + DPRINTF(1,("%s: ep%d bulk-%s start, xfer=%p, len=%u\n", DEVNAME(sc), + usbf_endpoint_index(pipe->endpoint), iswrite ? "in" : "out", + xfer, xfer->length)); + + s = splusb(); + xfer->status = USBF_IN_PROGRESS; + if (iswrite) + pxaudc_write(sc, xfer); + splx(s); + return USBF_IN_PROGRESS; +} + +void +pxaudc_bulk_abort(usbf_xfer_handle xfer) +{ + pxaudc_ctrl_abort(xfer); +} + +void +pxaudc_bulk_done(usbf_xfer_handle xfer) +{ +} + +void +pxaudc_bulk_close(usbf_pipe_handle pipe) +{ + /* XXX */ } diff --git a/sys/arch/arm/xscale/pxa27x_udcreg.h b/sys/arch/arm/xscale/pxa27x_udcreg.h index f54a0e42296..b9e2e4ad76b 100644 --- a/sys/arch/arm/xscale/pxa27x_udcreg.h +++ b/sys/arch/arm/xscale/pxa27x_udcreg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pxa27x_udcreg.h,v 1.1 2005/02/17 22:10:35 dlg Exp $ */ +/* $OpenBSD: pxa27x_udcreg.h,v 1.2 2006/11/25 18:10:29 uwe Exp $ */ /* * Copyright (c) 2005 David Gwynne <dlg@openbsd.org> @@ -149,4 +149,23 @@ #define USBDC_UDCECR_IN (7<<22) /* Interface Number */ #define USBDC_UDCECR_CN (3<<25) /* Configuration Number */ +#define USBDC_UDCCR_BITS \ + "\20\001UDE\002UDA\003UDR\004EMCE\005SMAC\021DWRE" \ + "\035BHNP\036AHNP\037OEN" +#define USBDC_UDCISR0_BITS \ + "\20\0010P\0020F\003AP\004AF\005BP\006BF\007CP\010CF" \ + "\011DP\012DF\013EP\014EF\015FP\016FF\017GP\020GF" \ + "\031HP\032HF\033IP\034IF\035JP\036JF\037KP\030KF" \ + "\041LP\042LF\043MP\044MF\045NP\046NF\047PP\040PF" +#define USBDC_UDCISR1_BITS \ + "\20\001QP\002QF\003RP\004RF\005SP\006SF\007TP\010TF" \ + "\011UP\012UF\013VP\014VF\015WP\016WF\017XP\020XF" \ + "\034RS\035SU\036RU\037SOF\040CC" +#define USBDC_UDCOTGISR_BITS \ + "\20\001IRIDF\002IRIDR\003IRSDF\004IRSDR\005IRSVF\006IRSVR" \ + "\007IRVV44F\010IRVV44R\011IRVV40F\012IRVV40R" +#define USBDC_UDCCSR0_BITS \ + "\20\001OPC\002IPR\003FTF\004DME\005SST\006FST\007RNE" \ + "\010SA\011AREN\012ACM" + #endif /* _ARM_XSCALE_PXA27X_UDCREG_H_ */ diff --git a/sys/arch/zaurus/conf/GENERIC b/sys/arch/zaurus/conf/GENERIC index d182a1c1d8c..494e4435b48 100644 --- a/sys/arch/zaurus/conf/GENERIC +++ b/sys/arch/zaurus/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.55 2006/11/25 14:31:59 uwe Exp $ +# $OpenBSD: GENERIC,v 1.56 2006/11/25 18:10:29 uwe Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -64,6 +64,10 @@ wskbd* at zkbd? mux 1 pxaudc0 at pxaip? # USB Device Controller ohci0 at pxaip? # Open Host Controller +# USB function support +#usbf* at pxaudc? # USB logical device +#cdcef* at usbf? # CDC ethernet function + # USB bus support usb* at ohci? uhub* at usb? # USB Root Hub diff --git a/sys/arch/zaurus/include/zaurus_reg.h b/sys/arch/zaurus/include/zaurus_reg.h index 27c42c5c562..fd4e40eb3ac 100644 --- a/sys/arch/zaurus/include/zaurus_reg.h +++ b/sys/arch/zaurus/include/zaurus_reg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: zaurus_reg.h,v 1.7 2005/12/14 14:39:38 uwe Exp $ */ +/* $OpenBSD: zaurus_reg.h,v 1.8 2006/11/25 18:10:29 uwe Exp $ */ /* $NetBSD: lubbock_reg.h,v 1.1 2003/06/18 10:51:15 bsh Exp $ */ /* @@ -75,5 +75,9 @@ #define C3000_RC_IRQ_PIN 13 /* remote control */ #define C3000_CF0_IRQ_PIN 94 #define C3000_CF1_IRQ_PIN 93 +#define C3000_USB_DEVICE_PIN 35 /* indicate connection type */ +#define C3000_USB_CONNECT_PIN 41 /* connection interrupt */ +#define C3000_USB_PULLUP_PIN 45 /* show/hide device presence */ +#define GPIO_HP_IN_C3000 116 /* headphone jack */ #endif /* _ZAURUS_REG_H */ diff --git a/sys/conf/files b/sys/conf/files index 8367c2dff7e..8352ee615c1 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.389 2006/11/25 14:31:59 uwe Exp $ +# $OpenBSD: files,v 1.390 2006/11/25 18:10:29 uwe Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -394,6 +394,7 @@ define eisabus {} # EISA attachment define pcibus {[bus = -1]} # PCI attachment define tcbus {} # TurboChannel attachment define usbus {} # USB attachment +define usbdev {} # USB function attachment define pcmciabus {[controller = -1], [socket = -1]} # PCMCIA attachment define cbbus {[slot = -1]} # CardBus attachment define pcmciaslot {[slot = -1]} # PCMCIA slot itself diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index 14030ef8330..38f7a403e72 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $OpenBSD: files.usb,v 1.65 2006/10/26 04:14:09 jsg Exp $ +# $OpenBSD: files.usb,v 1.66 2006/11/25 18:10:29 uwe Exp $ # $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $ # # Config file and device description for machine-independent USB code. @@ -10,7 +10,7 @@ attach usb at usbus file dev/usb/usb.c usb needs-flag file dev/usb/usbdi.c usb file dev/usb/usbdi_util.c usb -file dev/usb/usb_mem.c usb +file dev/usb/usb_mem.c usb | usbf file dev/usb/usb_subr.c usb file dev/usb/usb_quirks.c usb @@ -272,6 +272,19 @@ device ueagle: atm, ifnet, ezload, firmload attach ueagle at uhub file dev/usb/ueagle.c ueagle + +# USB logical device +device usbf {} +attach usbf at usbdev +file dev/usb/usbf.c usbf +file dev/usb/usbf_subr.c usbf + +# Communication Device Class Ethernet function +device cdcef {} +attach cdcef at usbf +file dev/usb/if_cdcef.c cdcef + + # Atheros AR5005UG/AR5005UX device uath: ether, ifnet, ifmedia, wlan, firmload attach uath at uhub diff --git a/sys/dev/usb/if_cdcef.c b/sys/dev/usb/if_cdcef.c new file mode 100644 index 00000000000..aaa9dc696a3 --- /dev/null +++ b/sys/dev/usb/if_cdcef.c @@ -0,0 +1,252 @@ +/* $OpenBSD: if_cdcef.c,v 1.1 2006/11/25 18:10:29 uwe 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 Communication Device Class Ethernet Emulation Model function driver + * (counterpart of the host-side cdce(4) driver) + */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/socket.h> +#include <sys/systm.h> + +#include <net/if.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbf.h> +#include <dev/usb/usbcdc.h> + +#define CDCEF_VENDOR_ID 0x0001 +#define CDCEF_PRODUCT_ID 0x0001 +#define CDCEF_DEVICE_CODE 0x0100 +#define CDCEF_VENDOR_STRING "OpenBSD.org" +#define CDCEF_PRODUCT_STRING "CDC Ethernet Emulation" +#define CDCEF_SERIAL_STRING "1.00" + +#define CDCEF_BUFSZ 65536 + +struct cdcef_softc { + struct usbf_function sc_dev; + usbf_config_handle sc_config; + usbf_interface_handle sc_iface; + usbf_endpoint_handle sc_ep_in; + usbf_endpoint_handle sc_ep_out; + usbf_pipe_handle sc_pipe_in; + usbf_pipe_handle sc_pipe_out; + usbf_xfer_handle sc_xfer_in; + usbf_xfer_handle sc_xfer_out; + void *sc_buffer_in; + void *sc_buffer_out; +}; + +int cdcef_match(struct device *, void *, void *); +void cdcef_attach(struct device *, struct device *, void *); + +usbf_status cdcef_do_request(usbf_function_handle, + usb_device_request_t *, void **); + +void cdcef_start(struct ifnet *); + +void cdcef_txeof(usbf_xfer_handle, usbf_private_handle, + usbf_status); +void cdcef_rxeof(usbf_xfer_handle, usbf_private_handle, + usbf_status); + +struct cfattach cdcef_ca = { + sizeof(struct cdcef_softc), cdcef_match, cdcef_attach +}; + +struct cfdriver cdcef_cd = { + NULL, "cdcef", DV_DULL +}; + +struct usbf_function_methods cdcef_methods = { + NULL, /* set_config */ + cdcef_do_request +}; + +#ifndef CDCEF_DEBUG +#define DPRINTF(x) do {} while (0) +#else +#define DPRINTF(x) printf x +#endif + +#define DEVNAME(sc) USBDEVNAME((sc)->sc_dev.bdev) + +/* + * USB function match/attach/detach + */ + +USB_MATCH(cdcef) +{ + return UMATCH_GENERIC; +} + +USB_ATTACH(cdcef) +{ + struct cdcef_softc *sc = (struct cdcef_softc *)self; + struct usbf_attach_arg *uaa = aux; + usbf_device_handle dev = uaa->device; + char *devinfop; + usbf_status err; + usb_cdc_union_descriptor_t udesc; + + /* Set the device identification according to the function. */ + usbf_devinfo_setup(dev, UDCLASS_IN_INTERFACE, 0, 0, CDCEF_VENDOR_ID, + CDCEF_PRODUCT_ID, CDCEF_DEVICE_CODE, CDCEF_VENDOR_STRING, + CDCEF_PRODUCT_STRING, CDCEF_SERIAL_STRING); + + devinfop = usbf_devinfo_alloc(dev); + printf(": %s\n", devinfop); + usbf_devinfo_free(devinfop); + + /* Fill in the fields needed by the parent device. */ + sc->sc_dev.methods = &cdcef_methods; + + /* + * Build descriptors according to the device class specification. + */ + err = usbf_add_config(dev, &sc->sc_config); + if (err) { + printf("%s: usbf_add_config failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + err = usbf_add_interface(sc->sc_config, UICLASS_CDC, + UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0, NULL, + &sc->sc_iface); + if (err) { + printf("%s: usbf_add_interface failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + /* XXX don't use hard-coded values 128 and 16. */ + err = usbf_add_endpoint(sc->sc_iface, UE_DIR_IN, UE_BULK, + 128, 16, &sc->sc_ep_in) || + usbf_add_endpoint(sc->sc_iface, UE_DIR_OUT, UE_BULK, + 128, 16, &sc->sc_ep_out); + if (err) { + printf("%s: usbf_add_endpoint failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + /* Append a CDC union descriptor. */ + bzero(&udesc, sizeof udesc); + udesc.bLength = sizeof udesc; + udesc.bDescriptorType = UDESC_CS_INTERFACE; + udesc.bDescriptorSubtype = UDESCSUB_CDC_UNION; + udesc.bSlaveInterface[0] = usbf_interface_number(sc->sc_iface); + err = usbf_add_config_desc(sc->sc_config, + (usb_descriptor_t *)&udesc, NULL); + if (err) { + printf("%s: usbf_add_config_desc failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + /* + * Close the configuration and build permanent descriptors. + */ + err = usbf_end_config(sc->sc_config); + if (err) { + printf("%s: usbf_end_config failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + /* Preallocate xfers and data buffers. */ + sc->sc_xfer_in = usbf_alloc_xfer(dev); + sc->sc_xfer_out = usbf_alloc_xfer(dev); + sc->sc_buffer_in = usbf_alloc_buffer(sc->sc_xfer_in, + CDCEF_BUFSZ); + sc->sc_buffer_out = usbf_alloc_buffer(sc->sc_xfer_out, + CDCEF_BUFSZ); + if (sc->sc_buffer_in == NULL || sc->sc_buffer_out == NULL) { + printf("%s: usbf_alloc_buffer failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + /* Open the bulk pipes. */ + err = usbf_open_pipe(sc->sc_iface, + usbf_endpoint_address(sc->sc_ep_in), &sc->sc_pipe_in) || + usbf_open_pipe(sc->sc_iface, + usbf_endpoint_address(sc->sc_ep_out), &sc->sc_pipe_out); + if (err) { + printf("%s: usbf_open_pipe failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + /* Get ready to receive packets. */ + usbf_setup_xfer(sc->sc_xfer_out, sc->sc_pipe_out, (void *)sc, + sc->sc_buffer_out, CDCEF_BUFSZ, 0, 0, cdcef_rxeof); + err = usbf_transfer(sc->sc_xfer_out); + if (err && err != USBF_IN_PROGRESS) { + printf("%s: usbf_transfer failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } + + USB_ATTACH_SUCCESS_RETURN; +} + +usbf_status +cdcef_do_request(usbf_function_handle fun, usb_device_request_t *req, + void **data) +{ + return USBF_STALLED; +} + +void +cdcef_start(struct ifnet *ifp) +{ +} + +void +cdcef_txeof(usbf_xfer_handle xfer, usbf_private_handle priv, + usbf_status err) +{ + struct cdcef_softc *sc = priv; + + printf("cdcef_txeof: xfer=%p, priv=%p, %s\n", xfer, priv, + usbf_errstr(err)); + + /* Setup another xfer. */ + usbf_setup_xfer(xfer, sc->sc_pipe_in, (void *)sc, + sc->sc_buffer_in, CDCEF_BUFSZ, 0, 0, cdcef_rxeof); + err = usbf_transfer(xfer); + if (err && err != USBF_IN_PROGRESS) { + printf("%s: usbf_transfer failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } +} + +void +cdcef_rxeof(usbf_xfer_handle xfer, usbf_private_handle priv, + usbf_status err) +{ + struct cdcef_softc *sc = priv; + + printf("cdcef_txeof: xfer=%p, priv=%p, %s\n", xfer, priv, + usbf_errstr(err)); + + /* Setup another xfer. */ + usbf_setup_xfer(xfer, sc->sc_pipe_out, (void *)sc, + sc->sc_buffer_out, CDCEF_BUFSZ, 0, 0, cdcef_rxeof); + err = usbf_transfer(xfer); + if (err && err != USBF_IN_PROGRESS) { + printf("%s: usbf_transfer failed\n", DEVNAME(sc)); + USB_ATTACH_ERROR_RETURN; + } +} diff --git a/sys/dev/usb/usbf.c b/sys/dev/usb/usbf.c new file mode 100644 index 00000000000..a1715323fbc --- /dev/null +++ b/sys/dev/usb/usbf.c @@ -0,0 +1,698 @@ +/* $OpenBSD: usbf.c,v 1.1 2006/11/25 18:10:29 uwe 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> + +#define USBF_DEBUG +#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 { + USBBASEDEVICE sc_dev; /* base device */ + usbf_bus_handle sc_bus; /* USB device controller */ + struct usbf_port sc_port; /* dummy port for function */ + usb_proc_ptr sc_proc; /* task thread */ + TAILQ_HEAD(,usbf_task) sc_tskq; /* task queue head */ + int sc_dying; +}; + +#define DEVNAME(sc) USBDEVNAME((sc)->sc_dev) + +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; + +USB_MATCH(usbf) +{ + return UMATCH_GENERIC; +} + +USB_ATTACH(usbf) +{ + 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; + USB_ATTACH_ERROR_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; + USB_ATTACH_ERROR_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; + USB_ATTACH_ERROR_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 + */ + +/* XXX */ +static u_int8_t hs_config[65536]; + +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; + + 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; + bcopy(cd, &hs_config, UGETW(cd->wTotalLength)); + *data = &hs_config; + ((usb_config_descriptor_t *)&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. */ + 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 diff --git a/sys/dev/usb/usbf_subr.c b/sys/dev/usb/usbf_subr.c new file mode 100644 index 00000000000..afb9512a66b --- /dev/null +++ b/sys/dev/usb/usbf_subr.c @@ -0,0 +1,1099 @@ +/* $OpenBSD: usbf_subr.c,v 1.1 2006/11/25 18:10:29 uwe 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 function driver interface subroutines + */ + +#include <sys/param.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/usb_mem.h> +#include <dev/usb/usbf.h> +#include <dev/usb/usbfvar.h> + +#define USBF_DEBUG +#ifndef USBF_DEBUG +#define DPRINTF(l, x) do {} while (0) +#else +extern int usbfdebug; +#define DPRINTF(l, x) if ((l) <= usbfdebug) printf x; else {} +#endif + +void *usbf_realloc(void **, size_t *, size_t); +size_t usbf_get_string(usbf_device_handle, u_int8_t, char *, size_t); +usbf_status usbf_open_pipe_ival(usbf_interface_handle, u_int8_t, + usbf_pipe_handle *, int); +usbf_status usbf_setup_pipe(usbf_device_handle, usbf_interface_handle, + struct usbf_endpoint *, int, + usbf_pipe_handle *); +void usbf_start_next(usbf_pipe_handle); +void usbf_set_endpoint_halt(usbf_endpoint_handle); +void usbf_clear_endpoint_halt(usbf_endpoint_handle); + +static const char * const usbf_error_strs[] = USBF_ERROR_STRS; + +const char * +usbf_errstr(usbf_status err) +{ + static char buffer[5]; + + if (err < USBD_ERROR_MAX) + return usbf_error_strs[err]; + + snprintf(buffer, sizeof buffer, "%d", err); + return buffer; +} + +void * +usbf_realloc(void **pp, size_t *sizep, size_t newsize) +{ + void *p; + size_t oldsize; + + if (newsize == 0) { + if (*sizep > 0) + free(*pp, M_USB); + *pp = NULL; + *sizep = 0; + return NULL; + } + + p = malloc(newsize, M_USB, M_NOWAIT); + if (p == NULL) + return NULL; + + oldsize = MIN(*sizep, newsize); + if (oldsize > 0) + bcopy(*pp, p, oldsize); + *pp = p; + *sizep = newsize; + return p; +} + +/* + * Attach a function driver. + */ +static usbf_status +usbf_probe_and_attach(device_ptr_t parent, usbf_device_handle dev, int port) +{ + struct usbf_attach_arg uaa; + device_ptr_t dv; + + KASSERT(dev->function == NULL); + + bzero(&uaa, sizeof uaa); + uaa.device = dev; + + /* + * The softc structure of a USB function driver must begin with a + * "struct usbf_function" member (instead of USBBASEDEV), which must + * be initialized in the function driver's attach routine. Also, it + * should use usbf_devinfo_setup() to set the device identification. + */ + dv = USB_DO_ATTACH(dev, NULL, parent, &uaa, NULL, NULL); + if (dv != NULL) { + dev->function = (struct usbf_function *)dv; + return USBF_NORMAL_COMPLETION; + } + + /* + * We failed to attach a function driver for this device, but the + * device can still function as a generic USB device without any + * interfaces. + */ + return USBF_NORMAL_COMPLETION; +} + +static void +usbf_remove_device(usbf_device_handle dev, struct usbf_port *up) +{ + KASSERT(dev != NULL && dev == up->device); + + if (dev->function != NULL) + config_detach((device_ptr_t)dev->function, DETACH_FORCE); + if (dev->default_pipe != NULL) + usbf_close_pipe(dev->default_pipe); + up->device = NULL; + free(dev, M_USB); +} + +usbf_status +usbf_new_device(device_ptr_t parent, usbf_bus_handle bus, int depth, + int speed, int port, struct usbf_port *up) +{ + struct usbf_device *dev; + usb_device_descriptor_t *ud; + usbf_status err; + +#ifdef DIAGNOSTIC + KASSERT(up->device == NULL); +#endif + + dev = malloc(sizeof(*dev), M_USB, M_NOWAIT); + if (dev == NULL) + return USBF_NOMEM; + + bzero(dev, sizeof *dev); + dev->bus = bus; + dev->string_id = USBF_STRING_ID_MIN; + SIMPLEQ_INIT(&dev->configs); + + /* Initialize device status. */ + USETW(dev->status.wStatus, UDS_SELF_POWERED); + + /* + * Initialize device descriptor. The function driver for this + * device (attached below) must complete the device descriptor. + */ + ud = &dev->ddesc; + ud->bLength = USB_DEVICE_DESCRIPTOR_SIZE; + ud->bDescriptorType = UDESC_DEVICE; + ud->bMaxPacketSize = bus->ep0_maxp; + if (bus->usbrev >= USBREV_2_0) + USETW(ud->bcdUSB, UD_USB_2_0); + else + USETW(ud->bcdUSB, 0x0101); + + /* Set up the default endpoint handle and descriptor. */ + dev->def_ep.edesc = &dev->def_ep_desc; + dev->def_ep_desc.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE; + dev->def_ep_desc.bDescriptorType = UDESC_ENDPOINT; + dev->def_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + dev->def_ep_desc.bmAttributes = UE_CONTROL; + USETW(dev->def_ep_desc.wMaxPacketSize, ud->bMaxPacketSize); + dev->def_ep_desc.bInterval = 0; + + /* Establish the default pipe. */ + err = usbf_setup_pipe(dev, NULL, &dev->def_ep, 0, + &dev->default_pipe); + if (err) { + free(dev, M_USB); + return err; + } + + /* Preallocate xfers for default pipe. */ + dev->default_xfer = usbf_alloc_xfer(dev); + dev->data_xfer = usbf_alloc_xfer(dev); + if (dev->default_xfer == NULL || dev->data_xfer == NULL) { + if (dev->default_xfer != NULL) + usbf_free_xfer(dev->default_xfer); + usbf_close_pipe(dev->default_pipe); + free(dev, M_USB); + return USBF_NOMEM; + } + + /* Insert device request xfer. */ + 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) { + usbf_free_xfer(dev->default_xfer); + usbf_free_xfer(dev->data_xfer); + usbf_close_pipe(dev->default_pipe); + free(dev, M_USB); + return err; + } + + /* Associate the upstream port with the device. */ + bzero(up, sizeof *up); + up->portno = port; + up->device = dev; + + /* Attach function driver. */ + err = usbf_probe_and_attach(parent, dev, port); + if (err) + usbf_remove_device(dev, up); + return err; +} + +/* + * Should be called by the function driver in its attach routine to change + * the default device identification according to the particular function. + */ +void +usbf_devinfo_setup(usbf_device_handle dev, u_int8_t devclass, + u_int8_t subclass, u_int8_t proto, u_int16_t vendor, u_int16_t product, + u_int16_t device, const char *manf, const char *prod, const char *ser) +{ + usb_device_descriptor_t *dd; + + dd = usbf_device_descriptor(dev); + dd->bDeviceClass = devclass; + dd->bDeviceSubClass = subclass; + dd->bDeviceProtocol = proto; + if (vendor != 0) + USETW(dd->idVendor, vendor); + if (product != 0) + USETW(dd->idProduct, product); + if (device != 0) + USETW(dd->bcdDevice, device); + if (manf != NULL) + dd->iManufacturer = usbf_add_string(dev, manf); + if (prod != NULL) + dd->iProduct = usbf_add_string(dev, prod); + if (ser != NULL) + dd->iSerialNumber = usbf_add_string(dev, ser); +} + +char * +usbf_devinfo_alloc(usbf_device_handle dev) +{ + char manf[40]; + char prod[40]; + usb_device_descriptor_t *dd; + size_t len; + char *devinfo; + + dd = usbf_device_descriptor(dev); + usbf_get_string(dev, dd->iManufacturer, manf, sizeof manf); + usbf_get_string(dev, dd->iProduct, prod, sizeof prod); + + len = strlen(manf) + strlen(prod) + 32; + devinfo = malloc(len, M_USB, M_NOWAIT); + if (devinfo == NULL) + return NULL; + + snprintf(devinfo, len, "%s %s, rev %d.%02d/%d.%02d", manf, prod, + (UGETW(dd->bcdUSB)>>8) & 0xff, UGETW(dd->bcdUSB) & 0xff, + (UGETW(dd->bcdDevice)>>8) & 0xff, UGETW(dd->bcdDevice) & 0xff); + return devinfo; +} + +void +usbf_devinfo_free(char *devinfo) +{ + if (devinfo != NULL) + free(devinfo, M_USB); +} + +/* + * Add a string descriptor to a logical device and return the string's id. + * + * If there is not enough memory available for the new string descriptor, or + * if there is no unused string id left, return the id of the empty string + * instead of failing. + */ +u_int8_t +usbf_add_string(usbf_device_handle dev, const char *string) +{ + usb_string_descriptor_t *sd; + size_t oldsize; + size_t newsize; + size_t len, i; + u_int8_t id; + + if (string == NULL || *string == '\0' || + dev->string_id == USBF_STRING_ID_MAX) + return USBF_EMPTY_STRING_ID; + + if ((len = strlen(string)) > USB_MAX_STRING_LEN) + len = USB_MAX_STRING_LEN; + + oldsize = dev->sdesc_size; + newsize = oldsize + 2 + 2 * len; + + sd = usbf_realloc((void **)&dev->sdesc, &dev->sdesc_size, + newsize); + if (sd == NULL) + return USBF_EMPTY_STRING_ID; + + sd = (usb_string_descriptor_t *)((char *)sd + oldsize); + sd->bLength = newsize - oldsize; + sd->bDescriptorType = UDESC_STRING; + for (i = 0; string[i] != '\0'; i++) + USETW(sd->bString[i], string[i]); + + id = dev->string_id++; + return id; +} + +usb_string_descriptor_t * +usbf_string_descriptor(usbf_device_handle dev, u_int8_t id) +{ + static usb_string_descriptor_t sd0; + static usb_string_descriptor_t sd1; + usb_string_descriptor_t *sd; + + /* handle the special string ids */ + switch (id) { + case USB_LANGUAGE_TABLE: + sd0.bLength = 4; + sd0.bDescriptorType = UDESC_STRING; + USETW(sd0.bString[0], 0x0409 /* en_US */); + return &sd0; + + case USBF_EMPTY_STRING_ID: + sd1.bLength = 2; + sd1.bDescriptorType = UDESC_STRING; + return &sd0; + } + + /* check if the string id is valid */ + if (id > dev->string_id) + return NULL; + + /* seek and return the descriptor of a non-empty string */ + id -= USBF_STRING_ID_MIN; + sd = dev->sdesc; + while (id-- > 0) + sd = (usb_string_descriptor_t *)((char *)sd + sd->bLength); + return sd; +} + +size_t +usbf_get_string(usbf_device_handle dev, u_int8_t id, char *s, size_t size) +{ + usb_string_descriptor_t *sd = NULL; + size_t i, len; + + if (id != USB_LANGUAGE_TABLE) + sd = usbf_string_descriptor(dev, id); + + if (sd == NULL) { + if (size > 0) + *s = '\0'; + return 0; + } + + len = (sd->bLength - 2) / 2; + if (size < 1) + return len; + + for (i = 0; i < (size - 1) && i < len; i++) + s[i] = UGETW(sd->bString[i]) & 0xff; + s[i] = '\0'; + return len; +} + +/* + * Add a new device configuration to an existing USB logical device. + * The new configuration initially has zero interfaces. + */ +usbf_status +usbf_add_config(usbf_device_handle dev, usbf_config_handle *ucp) +{ + struct usbf_config *uc; + usb_config_descriptor_t *cd; + + uc = malloc(sizeof *uc, M_USB, M_NOWAIT); + if (uc == NULL) + return USBF_NOMEM; + + cd = malloc(sizeof *cd, M_USB, M_NOWAIT); + if (cd == NULL) { + free(uc, M_USB); + return USBF_NOMEM; + } + + bzero(uc, sizeof *uc); + uc->uc_device = dev; + uc->uc_cdesc = cd; + uc->uc_cdesc_size = sizeof *cd; + SIMPLEQ_INIT(&uc->iface_head); + + bzero(cd, sizeof *cd); + cd->bLength = USB_CONFIG_DESCRIPTOR_SIZE; + cd->bDescriptorType = UDESC_CONFIG; + USETW(cd->wTotalLength, USB_CONFIG_DESCRIPTOR_SIZE); + cd->bConfigurationValue = USB_UNCONFIG_NO + 1 + + dev->ddesc.bNumConfigurations; + cd->iConfiguration = 0; + cd->bmAttributes = UC_BUS_POWERED | UC_SELF_POWERED; +#if 0 + cd->bMaxPower = 100 / UC_POWER_FACTOR; /* 100 mA */ +#else + cd->bMaxPower = 0; /* XXX 0 mA */ +#endif + + SIMPLEQ_INSERT_TAIL(&dev->configs, uc, next); + dev->ddesc.bNumConfigurations++; + + if (ucp != NULL) + *ucp = uc; + return USBF_NORMAL_COMPLETION; +} + +/* + * Allocate memory for a new descriptor at the end of the existing + * device configuration descriptor. + */ +usbf_status +usbf_add_config_desc(usbf_config_handle uc, usb_descriptor_t *d, + usb_descriptor_t **dp) +{ + usb_config_descriptor_t *cd; + size_t oldsize; + size_t newsize; + + oldsize = uc->uc_cdesc_size; + newsize = oldsize + d->bLength; + if (d->bLength < sizeof(usb_descriptor_t) || newsize > 65535) + return USBF_INVAL; + + cd = usbf_realloc((void **)&uc->uc_cdesc, &uc->uc_cdesc_size, + newsize); + if (cd == NULL) + return USBF_NOMEM; + + bcopy(d, (char *)cd + oldsize, d->bLength); + USETW(cd->wTotalLength, newsize); + if (dp != NULL) + *dp = (usb_descriptor_t *)((char *)cd + oldsize); + return USBF_NORMAL_COMPLETION; +} + +usbf_status +usbf_add_interface(usbf_config_handle uc, u_int8_t bInterfaceClass, + u_int8_t bInterfaceSubClass, u_int8_t bInterfaceProtocol, + const char *string, usbf_interface_handle *uip) +{ + struct usbf_interface *ui; + usb_interface_descriptor_t *id; + + if (uc->uc_closed) + return USBF_INVAL; + + ui = malloc(sizeof *ui, M_USB, M_NOWAIT); + if (ui == NULL) + return USBF_NOMEM; + + id = malloc(sizeof *id, M_USB, M_NOWAIT); + if (id == NULL) { + free(ui, M_USB); + return USBF_NOMEM; + } + + bzero(ui, sizeof *ui); + ui->config = uc; + ui->idesc = id; + LIST_INIT(&ui->pipes); + SIMPLEQ_INIT(&ui->endpoint_head); + + bzero(id, sizeof *id); + id->bLength = USB_INTERFACE_DESCRIPTOR_SIZE; + id->bDescriptorType = UDESC_INTERFACE; + id->bInterfaceNumber = uc->uc_cdesc->bNumInterface; + id->bInterfaceClass = bInterfaceClass; + id->bInterfaceSubClass = bInterfaceSubClass; + id->bInterfaceProtocol = bInterfaceProtocol; + id->iInterface = 0; /*usbf_add_string(uc->uc_device, string);*/ /* XXX */ + + SIMPLEQ_INSERT_TAIL(&uc->iface_head, ui, next); + uc->uc_cdesc->bNumInterface++; + + *uip = ui; + return USBF_NORMAL_COMPLETION; +} + +usbf_status +usbf_add_endpoint(usbf_interface_handle ui, u_int8_t bEndpointAddress, + u_int8_t bmAttributes, u_int16_t wMaxPacketSize, u_int8_t bInterval, + usbf_endpoint_handle *uep) +{ + struct usbf_endpoint *ue; + usb_endpoint_descriptor_t *ed; + + if (ui->config->uc_closed) + return USBF_INVAL; + + ue = malloc(sizeof *ue, M_USB, M_NOWAIT); + if (ue == NULL) + return USBF_NOMEM; + + ed = malloc(sizeof *ed, M_USB, M_NOWAIT); + if (ed == NULL) { + free(ue, M_USB); + return USBF_NOMEM; + } + + bzero(ue, sizeof *ue); + ue->iface = ui; + ue->edesc = ed; + + bzero(ed, sizeof *ed); + ed->bLength = USB_ENDPOINT_DESCRIPTOR_SIZE; + ed->bDescriptorType = UDESC_ENDPOINT; + ed->bEndpointAddress = bEndpointAddress; + ed->bmAttributes = bmAttributes; + USETW(ed->wMaxPacketSize, wMaxPacketSize); + ed->bInterval = bInterval; + + SIMPLEQ_INSERT_TAIL(&ui->endpoint_head, ue, next); + ui->idesc->bNumEndpoints++; + + *uep = ue; + return USBF_NORMAL_COMPLETION; +} + +/* + * Close the configuration, thereby combining all descriptors and creating + * the real USB configuration descriptor that can be sent to the USB host. + */ +usbf_status +usbf_end_config(usbf_config_handle uc) +{ + struct usbf_interface *ui; + struct usbf_endpoint *ue; + usb_descriptor_t *d; + usbf_status err = USBF_NORMAL_COMPLETION; + + if (uc->uc_closed) + return USBF_INVAL; + + SIMPLEQ_FOREACH(ui, &uc->iface_head, next) { + err = usbf_add_config_desc(uc, + (usb_descriptor_t *)ui->idesc, &d); + if (err) + break; + + free(ui->idesc, M_USB); + ui->idesc = (usb_interface_descriptor_t *)d; + + SIMPLEQ_FOREACH(ue, &ui->endpoint_head, next) { + ue->edesc->bEndpointAddress |= 1; /* XXX humbug */ + err = usbf_add_config_desc(uc, + (usb_descriptor_t *)ue->edesc, &d); + if (err) + break; + + free(ue->edesc, M_USB); + ue->edesc = (usb_endpoint_descriptor_t *)d; + } + } + + uc->uc_closed = 1; + return err; +} + +usb_device_descriptor_t * +usbf_device_descriptor(usbf_device_handle dev) +{ + return &dev->ddesc; +} + +usb_config_descriptor_t * +usbf_config_descriptor(usbf_device_handle dev, u_int8_t index) +{ + struct usbf_config *uc; + + SIMPLEQ_FOREACH(uc, &dev->configs, next) { + if (index-- == 0) + return uc->uc_cdesc; + } + return NULL; +} + +int +usbf_interface_number(usbf_interface_handle iface) +{ + return iface->idesc->bInterfaceNumber; +} + +u_int8_t +usbf_endpoint_address(usbf_endpoint_handle endpoint) +{ + return endpoint->edesc->bEndpointAddress; +} + +u_int8_t +usbf_endpoint_attributes(usbf_endpoint_handle endpoint) +{ + return endpoint->edesc->bmAttributes; +} + +usbf_status +usbf_open_pipe(usbf_interface_handle iface, u_int8_t address, + usbf_pipe_handle *pipe) +{ + return usbf_open_pipe_ival(iface, address, pipe, 0); +} + +usbf_status +usbf_open_pipe_ival(usbf_interface_handle iface, u_int8_t address, + usbf_pipe_handle *pipe, int ival) +{ + struct usbf_endpoint *ep; + usbf_pipe_handle p; + usbf_status err; + + ep = usbf_iface_endpoint(iface, address); + if (ep == NULL) + return USBF_BAD_ADDRESS; + + err = usbf_setup_pipe(iface->config->uc_device, iface, ep, + ival, &p); + if (err) + return err; + LIST_INSERT_HEAD(&iface->pipes, p, next); + *pipe = p; + return USBF_NORMAL_COMPLETION; +} + +usbf_status +usbf_setup_pipe(usbf_device_handle dev, usbf_interface_handle iface, + struct usbf_endpoint *ep, int ival, usbf_pipe_handle *pipe) +{ + struct usbf_pipe *p; + usbf_status err; + + p = malloc(dev->bus->pipe_size, M_USB, M_NOWAIT); + if (p == NULL) + return USBF_NOMEM; + + p->device = dev; + p->iface = iface; + p->endpoint = ep; + ep->refcnt++; + p->running = 0; + p->refcnt = 1; + p->repeat = 0; + p->interval = ival; + p->methods = NULL; /* set by bus driver in open_pipe() */ + SIMPLEQ_INIT(&p->queue); + err = dev->bus->methods->open_pipe(p); + if (err) { + free(p, M_USB); + return err; + } + *pipe = p; + return USBF_NORMAL_COMPLETION; +} + +/* Dequeue all pipe operations. */ +void +usbf_abort_pipe(usbf_pipe_handle pipe) +{ + usbf_xfer_handle xfer; + int s; + + s = splusb(); + pipe->repeat = 0; + pipe->aborting = 1; + + while ((xfer = SIMPLEQ_FIRST(&pipe->queue)) != NULL) { + DPRINTF(0,("usbf_abort_pipe: pipe=%p, xfer=%p\n", pipe, + xfer)); + /* Make the DC abort it (and invoke the callback). */ + pipe->methods->abort(xfer); + } + + pipe->aborting = 0; + splx(s); +} + +/* Abort all pipe operations and close the pipe. */ +void +usbf_close_pipe(usbf_pipe_handle pipe) +{ + usbf_abort_pipe(pipe); + pipe->methods->close(pipe); + pipe->endpoint->refcnt--; + free(pipe, M_USB); +} + +void +usbf_stall_pipe(usbf_pipe_handle pipe) +{ + DPRINTF(0,("usbf_stall_pipe not implemented\n")); +} + +usbf_endpoint_handle +usbf_iface_endpoint(usbf_interface_handle iface, u_int8_t address) +{ + usbf_endpoint_handle ep; + + SIMPLEQ_FOREACH(ep, &iface->endpoint_head, next) { + if (ep->edesc->bEndpointAddress == address) + return ep; + } + return NULL; +} + +usbf_endpoint_handle +usbf_config_endpoint(usbf_config_handle cfg, u_int8_t address) +{ + usbf_interface_handle iface; + usbf_endpoint_handle ep; + + SIMPLEQ_FOREACH(iface, &cfg->iface_head, next) { + SIMPLEQ_FOREACH(ep, &iface->endpoint_head, next) { + if (ep->edesc->bEndpointAddress == address) + return ep; + } + } + return NULL; +} + +void +usbf_set_endpoint_halt(usbf_endpoint_handle endpoint) +{ +} + +void +usbf_clear_endpoint_halt(usbf_endpoint_handle endpoint) +{ +} + +usbf_status +usbf_set_endpoint_feature(usbf_config_handle cfg, u_int8_t address, + u_int16_t value) +{ + usbf_endpoint_handle ep; + + DPRINTF(0,("usbf_set_endpoint_feature: cfg=%p address=%#x" + " value=%#x\n", cfg, address, value)); + + ep = usbf_config_endpoint(cfg, address); + if (ep == NULL) + return USBF_BAD_ADDRESS; + + switch (value) { + case UF_ENDPOINT_HALT: + usbf_set_endpoint_halt(ep); + return USBF_NORMAL_COMPLETION; + default: + /* unsupported feature, send STALL in data/status phase */ + return USBF_STALLED; + } +} + +usbf_status +usbf_clear_endpoint_feature(usbf_config_handle cfg, u_int8_t address, + u_int16_t value) +{ + usbf_endpoint_handle ep; + + DPRINTF(0,("usbf_clear_endpoint_feature: cfg=%p address=%#x" + " value=%#x\n", cfg, address, value)); + + ep = usbf_config_endpoint(cfg, address); + if (ep == NULL) + return USBF_BAD_ADDRESS; + + switch (value) { + case UF_ENDPOINT_HALT: + usbf_clear_endpoint_halt(ep); + return USBF_NORMAL_COMPLETION; + default: + /* unsupported feature, send STALL in data/status phase */ + return USBF_STALLED; + } +} + +usbf_xfer_handle +usbf_alloc_xfer(usbf_device_handle dev) +{ + struct usbf_xfer *xfer; + + /* allocate zero-filled buffer */ + xfer = dev->bus->methods->allocx(dev->bus); + if (xfer == NULL) + return NULL; + xfer->device = dev; + usb_callout_init(xfer->timeout_handle); + DPRINTF(1,("usbf_alloc_xfer() = %p\n", xfer)); + return xfer; +} + +void +usbf_free_xfer(usbf_xfer_handle xfer) +{ + DPRINTF(1,("usbf_free_xfer: %p\n", xfer)); + if (xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) + usbf_free_buffer(xfer); + xfer->device->bus->methods->freex(xfer->device->bus, xfer); +} + +usbf_status +usbf_allocmem(usbf_bus_handle bus, size_t size, size_t align, usb_dma_t *p) +{ + struct usbd_bus dbus; + usbd_status err; + + /* XXX bad idea, fix usb_mem.c instead! */ + dbus.dmatag = bus->dmatag; + err = usb_allocmem(&dbus, size, align, p); + return err ? USBF_NOMEM : USBF_NORMAL_COMPLETION; +} + +void +usbf_freemem(usbf_bus_handle bus, usb_dma_t *p) +{ + usb_freemem((usbd_bus_handle)NULL, p); +} + +void * +usbf_alloc_buffer(usbf_xfer_handle xfer, u_int32_t size) +{ + struct usbf_bus *bus = xfer->device->bus; + usbf_status err; + +#ifdef DIAGNOSTIC + if (xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) + printf("xfer %p already has a buffer\n", xfer); +#endif + + err = bus->methods->allocm(bus, &xfer->dmabuf, size); + if (err) + return NULL; + + xfer->rqflags |= URQ_DEV_DMABUF; + return KERNADDR(&xfer->dmabuf, 0); +} + +void +usbf_free_buffer(usbf_xfer_handle xfer) +{ +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_DEV_DMABUF)) { + printf("usbf_free_buffer: no buffer\n"); + return; + } +#endif + xfer->rqflags &= ~URQ_DEV_DMABUF; + xfer->device->bus->methods->freem(xfer->device->bus, &xfer->dmabuf); +} + +#ifdef USBF_DEBUG +/* + * The dump format is similar to Linux' Gadget driver so that we can + * easily compare traces. + */ +static void +usbf_dump_buffer(usbf_xfer_handle xfer) +{ + device_ptr_t dev = (device_ptr_t)xfer->pipe->device->bus->usbfctl; + usbf_endpoint_handle ep = xfer->pipe->endpoint; + int index = usbf_endpoint_index(ep); + int dir = usbf_endpoint_dir(ep); + u_char *p = xfer->buffer; + u_int i; + + printf("%s: ep%d-%s, length=%u, %s", USBDEVNAME(*dev), index, + (xfer->rqflags & URQ_REQUEST) ? "setup" : + (index == 0 ? "in" : (dir == UE_DIR_IN ? "in" : "out")), + xfer->length, usbf_errstr(xfer->status)); + + for (i = 0; i < xfer->length; i++) { + if ((i % 16) == 0) + printf("\n%4x:", i); + else if ((i % 8) == 0) + printf(" "); + printf(" %02x", p[i]); + } + printf("\n"); +} +#endif + +void +usbf_setup_xfer(usbf_xfer_handle xfer, usbf_pipe_handle pipe, + usbf_private_handle priv, void *buffer, u_int32_t length, + u_int16_t flags, u_int32_t timeout, usbf_callback callback) +{ + xfer->pipe = pipe; + xfer->priv = priv; + xfer->buffer = buffer; + xfer->length = length; + xfer->actlen = 0; + xfer->flags = flags; + xfer->timeout = timeout; + xfer->status = USBF_NOT_STARTED; + xfer->callback = callback; + xfer->rqflags &= ~URQ_REQUEST; +} + +void +usbf_setup_default_xfer(usbf_xfer_handle xfer, usbf_pipe_handle pipe, + usbf_private_handle priv, usb_device_request_t *req, u_int16_t flags, + u_int32_t timeout, usbf_callback callback) +{ + xfer->pipe = pipe; + xfer->priv = priv; + xfer->buffer = req; + xfer->length = sizeof *req; + xfer->actlen = 0; + xfer->flags = flags; + xfer->timeout = timeout; + xfer->status = USBF_NOT_STARTED; + xfer->callback = callback; + xfer->rqflags |= URQ_REQUEST; +} + +void +usbf_get_xfer_status(usbf_xfer_handle xfer, usbf_private_handle *priv, + void **buffer, u_int32_t *actlen, usbf_status *status) +{ + if (priv != NULL) + *priv = xfer->priv; + if (buffer != NULL) + *buffer = xfer->buffer; + if (actlen != NULL) + *actlen = xfer->actlen; + if (status != NULL) + *status = xfer->status; +} + +usbf_status +usbf_transfer(usbf_xfer_handle xfer) +{ + usbf_pipe_handle pipe = xfer->pipe; + usbf_status err; + + err = pipe->methods->transfer(xfer); + if (err != USBF_IN_PROGRESS && err) { + if (xfer->rqflags & URQ_AUTO_DMABUF) { + usbf_free_buffer(xfer); + xfer->rqflags &= ~URQ_AUTO_DMABUF; + } + } + return err; +} + +usbf_status +usbf_insert_transfer(usbf_xfer_handle xfer) +{ + usbf_pipe_handle pipe = xfer->pipe; + usbf_status err; + int s; + + DPRINTF(1,("usbf_insert_transfer: xfer=%p pipe=%p running=%d\n", + xfer, pipe, pipe->running)); + + s = splusb(); + SIMPLEQ_INSERT_TAIL(&pipe->queue, xfer, next); + if (pipe->running) + err = USBF_IN_PROGRESS; + else { + pipe->running = 1; + err = USBF_NORMAL_COMPLETION; + } + splx(s); + return err; +} + +void +usbf_start_next(usbf_pipe_handle pipe) +{ + usbf_xfer_handle xfer; + usbf_status err; + + SPLUSBCHECK; + + /* Get next request in queue. */ + xfer = SIMPLEQ_FIRST(&pipe->queue); + if (xfer == NULL) + pipe->running = 0; + else { + err = pipe->methods->start(xfer); + if (err != USBF_IN_PROGRESS) { + printf("usbf_start_next: %s\n", usbf_errstr(err)); + pipe->running = 0; + /* XXX do what? */ + } + } +} + +/* Called at splusb() */ +void +usbf_transfer_complete(usbf_xfer_handle xfer) +{ + usbf_pipe_handle pipe = xfer->pipe; + int repeat = pipe->repeat; + + SPLUSBCHECK; + DPRINTF(1,("usbf_transfer_complete: xfer=%p pipe=%p running=%d\n", + xfer, pipe, pipe->running)); +#ifdef USBF_DEBUG + if (usbfdebug > 0) + usbf_dump_buffer(xfer); +#endif + + if (!repeat) { + /* Remove request from queue. */ + KASSERT(SIMPLEQ_FIRST(&pipe->queue) == xfer); + SIMPLEQ_REMOVE_HEAD(&pipe->queue, next); + } + + if (xfer->status == USBF_NORMAL_COMPLETION && + xfer->actlen < xfer->length && + !(xfer->flags & USBD_SHORT_XFER_OK)) { + DPRINTF(0,("usbf_transfer_complete: short xfer=%p %u<%u\n", + xfer, xfer->actlen, xfer->length)); + xfer->status = USBF_SHORT_XFER; + } + + if (xfer->callback != NULL) + xfer->callback(xfer, xfer->priv, xfer->status); + + pipe->methods->done(xfer); + + /* XXX wake up any processes waiting for the transfer to complete */ + + if (!repeat) { + if (xfer->status != USBF_NORMAL_COMPLETION && + pipe->iface != NULL) /* not control pipe */ + pipe->running = 0; + else + usbf_start_next(pipe); + } +} + +/* + * Software interrupts + */ + +usbf_status +usbf_softintr_establish(struct usbf_bus *bus) +{ +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + KASSERT(bus->soft == NULL); + /* XXX we should have our own level */ + bus->soft = softintr_establish(IPL_SOFTNET, + bus->methods->soft_intr, bus); + if (bus->soft == NULL) + return USBF_INVAL; +#else + usb_callout_init(bus->softi); +#endif +#endif + return USBF_NORMAL_COMPLETION; +} + +void +usbf_schedsoftintr(struct usbf_bus *bus) +{ +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + softintr_schedule(bus->soft); +#else + if (!usb_callout_pending(bus->softi)) + usb_callout(bus->softi, 0, bus->methods->soft_intr, + bus); +#endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ +#else + bus->methods->soft_intr(bus); +#endif /* USB_USE_SOFTINTR */ +} diff --git a/sys/dev/usb/usbfvar.h b/sys/dev/usb/usbfvar.h new file mode 100644 index 00000000000..d36836b12c0 --- /dev/null +++ b/sys/dev/usb/usbfvar.h @@ -0,0 +1,175 @@ +/* $OpenBSD: usbfvar.h,v 1.1 2006/11/25 18:10:29 uwe 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 function driver interface + * + * This file is to be included only by the logical device driver and the + * USB device controller (DC) driver. + */ + +/*** structures corresponding to USB protocol components ***/ + +struct usbf_endpoint { + struct usbf_interface *iface; + usb_endpoint_descriptor_t *edesc; + int halted; /* UF_ENDPOINT_HALT set */ + int refcnt; + SIMPLEQ_ENTRY(usbf_endpoint) next; +}; + +struct usbf_interface { + struct usbf_config *config; + usb_interface_descriptor_t *idesc; + LIST_HEAD(, usbf_pipe) pipes; + SIMPLEQ_HEAD(, usbf_endpoint) endpoint_head; + SIMPLEQ_ENTRY(usbf_interface) next; +}; + +struct usbf_config { + struct usbf_device *uc_device; + usb_config_descriptor_t *uc_cdesc; + size_t uc_cdesc_size; + int uc_closed; + SIMPLEQ_HEAD(, usbf_interface) iface_head; + SIMPLEQ_ENTRY(usbf_config) next; +}; + +struct usbf_device { + USBBASEDEVICE bdev; /* base device */ + struct usbf_bus *bus; /* device controller */ + struct usbf_function *function; /* function driver */ + struct usbf_pipe *default_pipe; /* pipe 0 (device control) */ + struct usbf_xfer *default_xfer; /* device request xfer */ + struct usbf_xfer *data_xfer; /* request response xfer */ + int address; /* assigned by host (or 0) */ + usbf_config_handle config; /* set by host (or NULL) */ + usb_status_t status; /* device status */ + usb_device_request_t def_req; /* device request buffer */ + struct usbf_endpoint def_ep; /* for pipe 0 */ + usb_endpoint_descriptor_t def_ep_desc; /* for pipe 0 */ + usb_device_descriptor_t ddesc; /* device descriptor */ + usb_string_descriptor_t *sdesc; /* string descriptors */ + size_t sdesc_size; /* size of ud_sdesc */ + uByte string_id; /* next string id */ + SIMPLEQ_HEAD(, usbf_config) configs; +}; + +/*** software control structures ***/ + +struct usbf_pipe_methods { + usbf_status (*transfer)(usbf_xfer_handle); + usbf_status (*start)(usbf_xfer_handle); + void (*abort)(usbf_xfer_handle); + void (*done)(usbf_xfer_handle); + void (*close)(usbf_pipe_handle); +}; + +struct usbf_bus_methods { + usbf_status (*open_pipe)(struct usbf_pipe *); + void (*soft_intr)(void *); + usbf_status (*allocm)(struct usbf_bus *, usb_dma_t *, u_int32_t); + void (*freem)(struct usbf_bus *, usb_dma_t *); + struct usbf_xfer *(*allocx)(struct usbf_bus *); + void (*freex)(struct usbf_bus *, struct usbf_xfer *); +}; + +struct usbf_softc; + +struct usbf_bus { + /* Filled by DC driver */ + USBBASEDEVICE bdev; /* base device */ + struct usbf_bus_methods *methods; + size_t pipe_size; /* size of pipe struct */ + u_int8_t ep0_maxp; /* packet size for EP0 */ + int usbrev; /* as in struct usbd_bus */ + /* Filled by usbf driver */ + struct usbf_softc *usbfctl; + int intr_context; +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + void *soft; /* soft interrupt cookie */ +#else + usb_callout_t softi; /* timeout handle */ +#endif +#endif + bus_dma_tag_t dmatag; /* DMA tag */ +}; + +struct usbf_port { + usb_port_status_t status; + u_int8_t portno; + struct usbf_device *device; /* connected function */ +}; + +struct usbf_pipe { + struct usbf_device *device; + struct usbf_interface *iface; /* unless default pipe */ + struct usbf_endpoint *endpoint; + int refcnt; + int running; + int aborting; + SIMPLEQ_HEAD(, usbf_xfer) queue; + LIST_ENTRY(usbf_pipe) next; + + char repeat; + int interval; + + /* Filled by DC driver. */ + struct usbf_pipe_methods *methods; +}; + +struct usbf_xfer { + struct usbf_pipe *pipe; + usbf_private_handle priv; + void *buffer; + u_int32_t length; + u_int32_t actlen; + u_int16_t flags; + u_int32_t timeout; + usbf_status status; + usbf_callback callback; + SIMPLEQ_ENTRY(usbf_xfer) next; + + /* for memory management */ + struct usbf_device *device; + int rqflags; + usb_dma_t dmabuf; + + usb_callout_t timeout_handle; +}; + + +/* usbf.c */ +void usbf_host_reset(usbf_bus_handle); +void usbf_do_request(usbf_xfer_handle, usbf_private_handle, + usbf_status); + +/* usbf_subr.c */ +usbf_status usbf_new_device(device_ptr_t, usbf_bus_handle, int, + int, int, struct usbf_port *); +usbf_status usbf_set_endpoint_feature(usbf_config_handle, u_int8_t, + u_int16_t); +usbf_status usbf_clear_endpoint_feature(usbf_config_handle, u_int8_t, + u_int16_t); +usbf_status usbf_insert_transfer(usbf_xfer_handle xfer); +void usbf_transfer_complete(usbf_xfer_handle xfer); +usbf_status usbf_allocmem(usbf_bus_handle, size_t, size_t, usb_dma_t *); +void usbf_freemem(usbf_bus_handle, usb_dma_t *); +usbf_status usbf_softintr_establish(struct usbf_bus *); +void usbf_schedsoftintr(struct usbf_bus *); |