Index: head/sys/dev/usb/ugen.c =================================================================== --- head/sys/dev/usb/ugen.c (revision 53513) +++ head/sys/dev/usb/ugen.c (revision 53514) @@ -1,1174 +1,1186 @@ /* $NetBSD: ugen.c,v 1.27 1999/10/28 12:08:38 augustss Exp $ */ /* $FreeBSD$ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef UGEN_DEBUG #define DPRINTF(x) if (ugendebug) logprintf x #define DPRINTFN(n,x) if (ugendebug>(n)) logprintf x int ugendebug = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct ugen_endpoint { struct ugen_softc *sc; usb_endpoint_descriptor_t *edesc; usbd_interface_handle iface; int state; #define UGEN_ASLP 0x02 /* waiting for data */ #define UGEN_SHORT_OK 0x04 /* short xfers are OK */ usbd_pipe_handle pipeh; struct clist q; struct selinfo rsel; void *ibuf; u_int32_t timeout; }; #define UGEN_CHUNK 128 /* chunk size for read */ #define UGEN_IBSIZE 1020 /* buffer size */ #define UGEN_BBSIZE 1024 struct ugen_softc { USBBASEDEVICE sc_dev; /* base device */ usbd_device_handle sc_udev; char sc_is_open[USB_MAX_ENDPOINTS]; struct ugen_endpoint sc_endpoints[USB_MAX_ENDPOINTS][2]; #define OUT 0 #define IN 1 int sc_refcnt; u_char sc_dying; }; #if defined(__NetBSD__) || defined(__OpenBSD__) cdev_decl(ugen); #elif defined(__FreeBSD__) d_open_t ugenopen; d_close_t ugenclose; d_read_t ugenread; d_write_t ugenwrite; d_ioctl_t ugenioctl; d_poll_t ugenpoll; #define UGEN_CDEV_MAJOR 114 static struct cdevsw ugen_cdevsw = { /* open */ ugenopen, /* close */ ugenclose, /* read */ ugenread, /* write */ ugenwrite, /* ioctl */ ugenioctl, /* poll */ ugenpoll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "ugen", /* maj */ UGEN_CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; #endif static void ugenintr __P((usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status)); static int ugen_do_read __P((struct ugen_softc *, int, struct uio *, int)); static int ugen_do_write __P((struct ugen_softc *, int, struct uio *, int)); static int ugen_do_ioctl __P((struct ugen_softc *, int, u_long, caddr_t, int, struct proc *)); static int ugen_set_config __P((struct ugen_softc *sc, int configno)); static usb_config_descriptor_t *ugen_get_cdesc __P((struct ugen_softc *sc, int index, int *lenp)); static usbd_status ugen_set_interface __P((struct ugen_softc *, int, int)); static int ugen_get_alt_index __P((struct ugen_softc *sc, int ifaceidx)); +#define UGENENDPMAX 16 /* maximum number of endpoints, see usb spec */ #define UGENUNIT(n) ((minor(n) >> 4) & 0xf) #define UGENENDPOINT(n) (minor(n) & 0xf) -#define UGENDEV(u, e) (makedev(0, ((u) << 4) | (e))) +#define UGENDEV(u, e) (makedev(UGEN_CDEV_MAJOR, ((u) << 4) | (e))) USB_DECLARE_DRIVER(ugen); USB_MATCH(ugen) { USB_MATCH_START(ugen, uaa); if (uaa->usegeneric) return (UMATCH_GENERIC); else return (UMATCH_NONE); } USB_ATTACH(ugen) { USB_ATTACH_START(ugen, sc, uaa); char devinfo[1024]; usbd_status err; int conf; usbd_devinfo(uaa->device, 0, devinfo); USB_ATTACH_SETUP; printf("%s: %s\n", USBDEVNAME(sc->sc_dev), devinfo); sc->sc_udev = uaa->device; conf = 1; /* XXX should not hard code 1 */ err = ugen_set_config(sc, conf); if (err) { printf("%s: setting configuration %d failed\n", USBDEVNAME(sc->sc_dev), conf); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } #ifdef __FreeBSD__ { static int global_init_done = 0; if (!global_init_done) { cdevsw_add(&ugen_cdevsw); global_init_done = 1; } } #endif USB_ATTACH_SUCCESS_RETURN; } static int ugen_set_config(sc, configno) struct ugen_softc *sc; int configno; { usbd_device_handle dev = sc->sc_udev; usbd_interface_handle iface; usb_endpoint_descriptor_t *ed; struct ugen_endpoint *sce; u_int8_t niface, nendpt; int ifaceno, endptno, endpt; usbd_status err; int dir; DPRINTFN(1,("ugen_set_config: %s to configno %d, sc=%p\n", USBDEVNAME(sc->sc_dev), configno, sc)); if (usbd_get_config_descriptor(dev)->bConfigurationValue != configno) { /* Avoid setting the current value. */ err = usbd_set_config_no(dev, configno, 0); if (err) return (err); } err = usbd_interface_count(dev, &niface); if (err) return (err); memset(sc->sc_endpoints, 0, sizeof sc->sc_endpoints); for (ifaceno = 0; ifaceno < niface; ifaceno++) { DPRINTFN(1,("ugen_set_config: ifaceno %d\n", ifaceno)); err = usbd_device2interface_handle(dev, ifaceno, &iface); if (err) return (err); err = usbd_endpoint_count(iface, &nendpt); if (err) return (err); for (endptno = 0; endptno < nendpt; endptno++) { ed = usbd_interface2endpoint_descriptor(iface,endptno); endpt = ed->bEndpointAddress; dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; DPRINTFN(1,("ugen_set_config: endptno %d, endpt=0x%02x" "(%d,%d), sce=%p\n", endptno, endpt, UE_GET_ADDR(endpt), UE_GET_DIR(endpt), sce)); sce->sc = sc; sce->edesc = ed; sce->iface = iface; } } return (USBD_NORMAL_COMPLETION); } int ugenopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct ugen_softc *sc; int unit = UGENUNIT(dev); int endpt = UGENENDPOINT(dev); usb_endpoint_descriptor_t *edesc; struct ugen_endpoint *sce; int dir, isize; usbd_status err; USB_GET_SC_OPEN(ugen, unit, sc); DPRINTFN(5, ("ugenopen: flag=%d, mode=%d, unit=%d endpt=%d\n", flag, mode, unit, endpt)); if (sc == NULL || sc->sc_dying) return (ENXIO); if (sc->sc_is_open[endpt]) return (EBUSY); if (endpt == USB_CONTROL_ENDPOINT) { sc->sc_is_open[USB_CONTROL_ENDPOINT] = 1; return (0); } /* Make sure there are pipes for all directions. */ for (dir = OUT; dir <= IN; dir++) { if (flag & (dir == OUT ? FWRITE : FREAD)) { sce = &sc->sc_endpoints[endpt][dir]; if (sce == 0 || sce->edesc == 0) return (ENXIO); } } /* Actually open the pipes. */ /* XXX Should back out properly if it fails. */ for (dir = OUT; dir <= IN; dir++) { if (!(flag & (dir == OUT ? FWRITE : FREAD))) continue; sce = &sc->sc_endpoints[endpt][dir]; sce->state = 0; sce->timeout = USBD_NO_TIMEOUT; DPRINTFN(5, ("ugenopen: sc=%p, endpt=%d, dir=%d, sce=%p\n", sc, endpt, dir, sce)); edesc = sce->edesc; switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: isize = UGETW(edesc->wMaxPacketSize); if (isize == 0) /* shouldn't happen */ return (EINVAL); sce->ibuf = malloc(isize, M_USBDEV, M_WAITOK); DPRINTFN(5, ("ugenopen: intr endpt=%d,isize=%d\n", endpt, isize)); if (clalloc(&sce->q, UGEN_IBSIZE, UGEN_IBSIZE) == -1) return (ENOMEM); err = usbd_open_pipe_intr(sce->iface, edesc->bEndpointAddress, USBD_SHORT_XFER_OK, &sce->pipeh, sce, sce->ibuf, isize, ugenintr); if (err) { free(sce->ibuf, M_USBDEV); clfree(&sce->q); return (EIO); } DPRINTFN(5, ("ugenopen: interrupt open done\n")); break; case UE_BULK: err = usbd_open_pipe(sce->iface, edesc->bEndpointAddress, 0, &sce->pipeh); if (err) return (EIO); break; case UE_CONTROL: case UE_ISOCHRONOUS: return (EINVAL); } } sc->sc_is_open[endpt] = 1; return (0); } int ugenclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { int endpt = UGENENDPOINT(dev); struct ugen_softc *sc; struct ugen_endpoint *sce; int dir; USB_GET_SC(ugen, UGENUNIT(dev), sc); DPRINTFN(5, ("ugenclose: flag=%d, mode=%d, unit=%d, endpt=%d\n", flag, mode, UGENUNIT(dev), endpt)); #ifdef DIAGNOSTIC if (!sc->sc_is_open[endpt]) { printf("ugenclose: not open\n"); return (EINVAL); } #endif if (endpt == USB_CONTROL_ENDPOINT) { DPRINTFN(5, ("ugenclose: close control\n")); sc->sc_is_open[endpt] = 0; return (0); } for (dir = OUT; dir <= IN; dir++) { if (!(flag & (dir == OUT ? FWRITE : FREAD))) continue; sce = &sc->sc_endpoints[endpt][dir]; if (sce == NULL || sce->pipeh == NULL) continue; DPRINTFN(5, ("ugenclose: endpt=%d dir=%d sce=%p\n", endpt, dir, sce)); usbd_abort_pipe(sce->pipeh); usbd_close_pipe(sce->pipeh); sce->pipeh = NULL; if (sce->ibuf != NULL) { free(sce->ibuf, M_USBDEV); sce->ibuf = NULL; clfree(&sce->q); } } sc->sc_is_open[endpt] = 0; return (0); } static int ugen_do_read(sc, endpt, uio, flag) struct ugen_softc *sc; int endpt; struct uio *uio; int flag; { struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][IN]; u_int32_t n, tn; char buf[UGEN_BBSIZE]; usbd_xfer_handle xfer; usbd_status err; int s; int error = 0; u_char buffer[UGEN_CHUNK]; #ifdef __NetBSD__ DPRINTFN(5, ("ugenread: %d:%d\n", sc->sc_dev.dv_unit, endpt)); #endif if (sc->sc_dying) return (EIO); if (endpt == USB_CONTROL_ENDPOINT) return (ENODEV); #ifdef DIAGNOSTIC if (sce->edesc == NULL) { printf("ugenread: no edesc\n"); return (EIO); } if (sce->pipeh == NULL) { printf("ugenread: no pipe\n"); return (EIO); } #endif switch (sce->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: /* Block until activity occured. */ s = splusb(); while (sce->q.c_cc == 0) { if (flag & IO_NDELAY) { splx(s); return (EWOULDBLOCK); } sce->state |= UGEN_ASLP; DPRINTFN(5, ("ugenread: sleep on %p\n", sc)); error = tsleep(sce, PZERO | PCATCH, "ugenri", 0); DPRINTFN(5, ("ugenread: woke, error=%d\n", error)); if (sc->sc_dying) error = EIO; if (error) { sce->state &= ~UGEN_ASLP; break; } } splx(s); /* Transfer as many chunks as possible. */ while (sce->q.c_cc > 0 && uio->uio_resid > 0 && !error) { n = min(sce->q.c_cc, uio->uio_resid); if (n > sizeof(buffer)) n = sizeof(buffer); /* Remove a small chunk from the input queue. */ q_to_b(&sce->q, buffer, n); DPRINTFN(5, ("ugenread: got %d chars\n", n)); /* Copy the data to the user process. */ error = uiomove(buffer, n, uio); if (error) break; } break; case UE_BULK: xfer = usbd_alloc_request(sc->sc_udev); if (xfer == 0) return (ENOMEM); while ((n = min(UGEN_BBSIZE, uio->uio_resid)) != 0) { DPRINTFN(1, ("ugenread: start transfer %d bytes\n",n)); tn = n; err = usbd_bulk_transfer( xfer, sce->pipeh, sce->state & UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0, sce->timeout, buf, &tn, "ugenrb"); if (err) { if (err == USBD_INTERRUPTED) error = EINTR; else if (err == USBD_TIMEOUT) error = ETIMEDOUT; else error = EIO; break; } DPRINTFN(1, ("ugenread: got %d bytes\n", tn)); error = uiomove(buf, tn, uio); if (error || tn < n) break; } usbd_free_request(xfer); break; default: return (ENXIO); } return (error); } int ugenread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int endpt = UGENENDPOINT(dev); struct ugen_softc *sc; int error; USB_GET_SC(ugen, UGENUNIT(dev), sc); sc->sc_refcnt++; error = ugen_do_read(sc, endpt, uio, flag); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } static int ugen_do_write(sc, endpt, uio, flag) struct ugen_softc *sc; int endpt; struct uio *uio; int flag; { struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][OUT]; u_int32_t n; int error = 0; char buf[UGEN_BBSIZE]; usbd_xfer_handle xfer; usbd_status err; DPRINTFN(5, ("%s: ugenwrite: %d\n", USBDEVNAME(sc->sc_dev), endpt)); if (sc->sc_dying) return (EIO); if (endpt == USB_CONTROL_ENDPOINT) return (ENODEV); #ifdef DIAGNOSTIC if (sce->edesc == NULL) { printf("ugenwrite: no edesc\n"); return (EIO); } if (sce->pipeh == NULL) { printf("ugenwrite: no pipe\n"); return (EIO); } #endif switch (sce->edesc->bmAttributes & UE_XFERTYPE) { case UE_BULK: xfer = usbd_alloc_request(sc->sc_udev); if (xfer == 0) return (EIO); while ((n = min(UGEN_BBSIZE, uio->uio_resid)) != 0) { error = uiomove(buf, n, uio); if (error) break; DPRINTFN(1, ("ugenwrite: transfer %d bytes\n", n)); err = usbd_bulk_transfer(xfer, sce->pipeh, 0, sce->timeout, buf, &n,"ugenwb"); if (err) { if (err == USBD_INTERRUPTED) error = EINTR; else error = EIO; break; } } usbd_free_request(xfer); break; default: return (ENXIO); } return (error); } int ugenwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int endpt = UGENENDPOINT(dev); struct ugen_softc *sc; int error; USB_GET_SC(ugen, UGENUNIT(dev), sc); sc->sc_refcnt++; error = ugen_do_write(sc, endpt, uio, flag); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } #if defined(__NetBSD__) || defined(__OpenBSD__) int ugen_activate(self, act) device_ptr_t self; enum devact act; { struct ugen_softc *sc = (struct ugen_softc *)self; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); break; case DVACT_DEACTIVATE: sc->sc_dying = 1; break; } return (0); } #endif USB_DETACH(ugen) { USB_DETACH_START(ugen, sc); struct ugen_endpoint *sce; int i, dir; int s; #if defined(__NetBSD__) || defined(__OpenBSD__) int maj, mn; +#elif defined(__FreeBSD__) + struct vnode *vp; + dev_t dev; + int endpt; +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) DPRINTF(("ugen_detach: sc=%p flags=%d\n", sc, flags)); #elif defined(__FreeBSD__) DPRINTF(("ugen_detach: sc=%p\n", sc)); #endif sc->sc_dying = 1; /* Abort all pipes. Causes processes waiting for transfer to wake. */ for (i = 0; i < USB_MAX_ENDPOINTS; i++) { for (dir = OUT; dir <= IN; dir++) { sce = &sc->sc_endpoints[i][dir]; if (sce && sce->pipeh) usbd_abort_pipe(sce->pipeh); } } s = splusb(); if (--sc->sc_refcnt >= 0) { /* Wake everyone */ for (i = 0; i < USB_MAX_ENDPOINTS; i++) wakeup(&sc->sc_endpoints[i][IN]); /* Wait for processes to go away. */ usb_detach_wait(USBDEV(sc->sc_dev)); } splx(s); #if defined(__NetBSD__) || defined(__OpenBSD__) /* locate the major number */ for (maj = 0; maj < nchrdev; maj++) if (cdevsw[maj].d_open == ugenopen) break; /* Nuke the vnodes for any open instances (calls close). */ mn = self->dv_unit * USB_MAX_ENDPOINTS; vdevgone(maj, mn, mn + USB_MAX_ENDPOINTS - 1, VCHR); #elif defined(__FreeBSD__) - /* XXX not implemented yet */ + for (endpt = 0; endpt < UGENENDPMAX; endpt++) { + dev = UGENDEV(device_get_unit(self), endpt); + vp = SLIST_FIRST(&dev->si_hlist); + if (vp) + VOP_REVOKE(vp, REVOKEALL); + } #endif return (0); } static void ugenintr(xfer, addr, status) usbd_xfer_handle xfer; usbd_private_handle addr; usbd_status status; { struct ugen_endpoint *sce = addr; /*struct ugen_softc *sc = sce->sc;*/ u_int32_t count; u_char *ibuf; if (status == USBD_CANCELLED) return; if (status != USBD_NORMAL_COMPLETION) { DPRINTF(("ugenintr: status=%d\n", status)); usbd_clear_endpoint_stall_async(sce->pipeh); return; } usbd_get_request_status(xfer, 0, 0, &count, 0); ibuf = sce->ibuf; DPRINTFN(5, ("ugenintr: xfer=%p status=%d count=%d\n", xfer, status, count)); DPRINTFN(5, (" data = %02x %02x %02x\n", ibuf[0], ibuf[1], ibuf[2])); (void)b_to_q(ibuf, count, &sce->q); if (sce->state & UGEN_ASLP) { sce->state &= ~UGEN_ASLP; DPRINTFN(5, ("ugen_intr: waking %p\n", sce)); wakeup(sce); } selwakeup(&sce->rsel); } static usbd_status ugen_set_interface(sc, ifaceidx, altno) struct ugen_softc *sc; int ifaceidx, altno; { usbd_interface_handle iface; usb_endpoint_descriptor_t *ed; usbd_status err; struct ugen_endpoint *sce; u_int8_t niface, nendpt, endptno, endpt; int dir; DPRINTFN(15, ("ugen_set_interface %d %d\n", ifaceidx, altno)); err = usbd_interface_count(sc->sc_udev, &niface); if (err) return (err); if (ifaceidx < 0 || ifaceidx >= niface) return (USBD_INVAL); err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface); if (err) return (err); err = usbd_endpoint_count(iface, &nendpt); if (err) return (err); for (endptno = 0; endptno < nendpt; endptno++) { ed = usbd_interface2endpoint_descriptor(iface,endptno); endpt = ed->bEndpointAddress; dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; sce->sc = 0; sce->edesc = 0; sce->iface = 0; } /* change setting */ err = usbd_set_interface(iface, altno); if (err) return (err); err = usbd_endpoint_count(iface, &nendpt); if (err) return (err); for (endptno = 0; endptno < nendpt; endptno++) { ed = usbd_interface2endpoint_descriptor(iface,endptno); endpt = ed->bEndpointAddress; dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; sce->sc = sc; sce->edesc = ed; sce->iface = iface; } return (0); } /* Retrieve a complete descriptor for a certain device and index. */ static usb_config_descriptor_t * ugen_get_cdesc(sc, index, lenp) struct ugen_softc *sc; int index; int *lenp; { usb_config_descriptor_t *cdesc, *tdesc, cdescr; int len; usbd_status err; if (index == USB_CURRENT_CONFIG_INDEX) { tdesc = usbd_get_config_descriptor(sc->sc_udev); len = UGETW(tdesc->wTotalLength); if (lenp) *lenp = len; cdesc = malloc(len, M_TEMP, M_WAITOK); memcpy(cdesc, tdesc, len); DPRINTFN(5,("ugen_get_cdesc: current, len=%d\n", len)); } else { err = usbd_get_config_desc(sc->sc_udev, index, &cdescr); if (err) return (0); len = UGETW(cdescr.wTotalLength); DPRINTFN(5,("ugen_get_cdesc: index=%d, len=%d\n", index, len)); if (lenp) *lenp = len; cdesc = malloc(len, M_TEMP, M_WAITOK); err = usbd_get_config_desc_full(sc->sc_udev, index, cdesc, len); if (err) { free(cdesc, M_TEMP); return (0); } } return (cdesc); } static int ugen_get_alt_index(sc, ifaceidx) struct ugen_softc *sc; int ifaceidx; { usbd_interface_handle iface; usbd_status err; err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface); if (err) return (-1); return (usbd_get_interface_altindex(iface)); } static int ugen_do_ioctl(sc, endpt, cmd, addr, flag, p) struct ugen_softc *sc; int endpt; u_long cmd; caddr_t addr; int flag; struct proc *p; { struct ugen_endpoint *sce; usbd_status err; usbd_interface_handle iface; struct usb_config_desc *cd; usb_config_descriptor_t *cdesc; struct usb_interface_desc *id; usb_interface_descriptor_t *idesc; struct usb_endpoint_desc *ed; usb_endpoint_descriptor_t *edesc; struct usb_alt_interface *ai; struct usb_string_desc *si; u_int8_t conf, alt; DPRINTFN(5, ("ugenioctl: cmd=%08lx\n", cmd)); if (sc->sc_dying) return (EIO); switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case USB_SET_SHORT_XFER: /* This flag only affects read */ if (endpt == USB_CONTROL_ENDPOINT) return (EINVAL); sce = &sc->sc_endpoints[endpt][IN]; if (sce == NULL) return (EINVAL); #ifdef DIAGNOSTIC if (sce->pipeh == NULL) { printf("ugenioctl: USB_SET_SHORT_XFER, no pipe\n"); return (EIO); } #endif if (*(int *)addr) sce->state |= UGEN_SHORT_OK; else sce->state &= ~UGEN_SHORT_OK; return (0); case USB_SET_TIMEOUT: sce = &sc->sc_endpoints[endpt][IN]; if (sce == NULL) return (EINVAL); #ifdef DIAGNOSTIC if (sce->pipeh == NULL) { printf("ugenioctl: USB_SET_TIMEOUT, no pipe\n"); return (EIO); } #endif sce->timeout = *(int *)addr; return (0); default: break; } if (endpt != USB_CONTROL_ENDPOINT) return (EINVAL); switch (cmd) { #ifdef UGEN_DEBUG case USB_SETDEBUG: ugendebug = *(int *)addr; break; #endif case USB_GET_CONFIG: err = usbd_get_config(sc->sc_udev, &conf); if (err) return (EIO); *(int *)addr = conf; break; case USB_SET_CONFIG: if (!(flag & FWRITE)) return (EPERM); err = ugen_set_config(sc, *(int *)addr); if (err) return (EIO); break; case USB_GET_ALTINTERFACE: ai = (struct usb_alt_interface *)addr; err = usbd_device2interface_handle(sc->sc_udev, ai->interface_index, &iface); if (err) return (EINVAL); idesc = usbd_get_interface_descriptor(iface); if (idesc == NULL) return (EIO); ai->alt_no = idesc->bAlternateSetting; break; case USB_SET_ALTINTERFACE: if (!(flag & FWRITE)) return (EPERM); ai = (struct usb_alt_interface *)addr; err = usbd_device2interface_handle(sc->sc_udev, ai->interface_index, &iface); if (err) return (EINVAL); err = ugen_set_interface(sc, ai->interface_index, ai->alt_no); if (err) return (EINVAL); break; case USB_GET_NO_ALT: ai = (struct usb_alt_interface *)addr; cdesc = ugen_get_cdesc(sc, ai->config_index, 0); if (cdesc == NULL) return (EINVAL); idesc = usbd_find_idesc(cdesc, ai->interface_index, 0); if (idesc == NULL) { free(cdesc, M_TEMP); return (EINVAL); } ai->alt_no = usbd_get_no_alts(cdesc, idesc->bInterfaceNumber); free(cdesc, M_TEMP); break; case USB_GET_DEVICE_DESC: *(usb_device_descriptor_t *)addr = *usbd_get_device_descriptor(sc->sc_udev); break; case USB_GET_CONFIG_DESC: cd = (struct usb_config_desc *)addr; cdesc = ugen_get_cdesc(sc, cd->config_index, 0); if (cdesc == NULL) return (EINVAL); cd->desc = *cdesc; free(cdesc, M_TEMP); break; case USB_GET_INTERFACE_DESC: id = (struct usb_interface_desc *)addr; cdesc = ugen_get_cdesc(sc, id->config_index, 0); if (cdesc == NULL) return (EINVAL); if (id->config_index == USB_CURRENT_CONFIG_INDEX && id->alt_index == USB_CURRENT_ALT_INDEX) alt = ugen_get_alt_index(sc, id->interface_index); else alt = id->alt_index; idesc = usbd_find_idesc(cdesc, id->interface_index, alt); if (idesc == NULL) { free(cdesc, M_TEMP); return (EINVAL); } id->desc = *idesc; free(cdesc, M_TEMP); break; case USB_GET_ENDPOINT_DESC: ed = (struct usb_endpoint_desc *)addr; cdesc = ugen_get_cdesc(sc, ed->config_index, 0); if (cdesc == NULL) return (EINVAL); if (ed->config_index == USB_CURRENT_CONFIG_INDEX && ed->alt_index == USB_CURRENT_ALT_INDEX) alt = ugen_get_alt_index(sc, ed->interface_index); else alt = ed->alt_index; edesc = usbd_find_edesc(cdesc, ed->interface_index, alt, ed->endpoint_index); if (edesc == NULL) { free(cdesc, M_TEMP); return (EINVAL); } ed->desc = *edesc; free(cdesc, M_TEMP); break; case USB_GET_FULL_DESC: { int len; struct iovec iov; struct uio uio; struct usb_full_desc *fd = (struct usb_full_desc *)addr; int error; cdesc = ugen_get_cdesc(sc, fd->config_index, &len); if (len > fd->size) len = fd->size; iov.iov_base = (caddr_t)fd->data; iov.iov_len = len; uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_resid = len; uio.uio_offset = 0; uio.uio_segflg = UIO_USERSPACE; uio.uio_rw = UIO_READ; uio.uio_procp = p; #if defined(__NetBSD__) || defined(__OpenBSD__) error = uiomove((caddr_t)cdesc, len, &uio); #elif defined(__FreeBSD__) error = uiomove((void *)cdesc, len, &uio); #endif free(cdesc, M_TEMP); return (error); } case USB_GET_STRING_DESC: si = (struct usb_string_desc *)addr; err = usbd_get_string_desc(sc->sc_udev, si->string_index, si->language_id, &si->desc); if (err) return (EINVAL); break; case USB_DO_REQUEST: { struct usb_ctl_request *ur = (void *)addr; int len = UGETW(ur->request.wLength); struct iovec iov; struct uio uio; void *ptr = 0; usbd_status err; int error = 0; if (!(flag & FWRITE)) return (EPERM); /* Avoid requests that would damage the bus integrity. */ if ((ur->request.bmRequestType == UT_WRITE_DEVICE && ur->request.bRequest == UR_SET_ADDRESS) || (ur->request.bmRequestType == UT_WRITE_DEVICE && ur->request.bRequest == UR_SET_CONFIG) || (ur->request.bmRequestType == UT_WRITE_INTERFACE && ur->request.bRequest == UR_SET_INTERFACE)) return (EINVAL); if (len < 0 || len > 32767) return (EINVAL); if (len != 0) { iov.iov_base = (caddr_t)ur->data; iov.iov_len = len; uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_resid = len; uio.uio_offset = 0; uio.uio_segflg = UIO_USERSPACE; uio.uio_rw = ur->request.bmRequestType & UT_READ ? UIO_READ : UIO_WRITE; uio.uio_procp = p; ptr = malloc(len, M_TEMP, M_WAITOK); if (uio.uio_rw == UIO_WRITE) { error = uiomove(ptr, len, &uio); if (error) goto ret; } } err = usbd_do_request_flags(sc->sc_udev, &ur->request, ptr, ur->flags, &ur->actlen); if (err) { error = EIO; goto ret; } if (len != 0) { if (uio.uio_rw == UIO_READ) { error = uiomove(ptr, len, &uio); if (error) goto ret; } } ret: if (ptr) free(ptr, M_TEMP); return (error); } case USB_GET_DEVICEINFO: usbd_fill_deviceinfo(sc->sc_udev, (struct usb_device_info *)addr); break; default: return (EINVAL); } return (0); } int ugenioctl(dev, cmd, addr, flag, p) dev_t dev; u_long cmd; caddr_t addr; int flag; struct proc *p; { int endpt = UGENENDPOINT(dev); struct ugen_softc *sc; int error; USB_GET_SC(ugen, UGENUNIT(dev), sc); sc->sc_refcnt++; error = ugen_do_ioctl(sc, endpt, cmd, addr, flag, p); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } int ugenpoll(dev, events, p) dev_t dev; int events; struct proc *p; { struct ugen_softc *sc; struct ugen_endpoint *sce; int revents = 0; int s; USB_GET_SC(ugen, UGENUNIT(dev), sc); if (sc->sc_dying) return (EIO); /* XXX always IN */ sce = &sc->sc_endpoints[UGENENDPOINT(dev)][IN]; if (sce == NULL) return (EINVAL); #ifdef DIAGNOSTIC if (!sce->edesc) { printf("ugenwrite: no edesc\n"); return (EIO); } if (!sce->pipeh) { printf("ugenpoll: no pipe\n"); return (EIO); } #endif s = splusb(); switch (sce->edesc->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: if (events & (POLLIN | POLLRDNORM)) { if (sce->q.c_cc > 0) revents |= events & (POLLIN | POLLRDNORM); else selrecord(p, &sce->rsel); } break; case UE_BULK: /* * We have no easy way of determining if a read will * yield any data or a write will happen. * Pretend they will. */ revents |= events & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM); break; default: break; } splx(s); return (revents); } #if defined(__FreeBSD__) DRIVER_MODULE(ugen, uhub, ugen_driver, ugen_devclass, usbd_driver_load, 0); #endif Index: head/sys/dev/usb/uhid.c =================================================================== --- head/sys/dev/usb/uhid.c (revision 53513) +++ head/sys/dev/usb/uhid.c (revision 53514) @@ -1,679 +1,687 @@ /* $NetBSD: uhid.c,v 1.26 1999/10/13 08:10:56 augustss Exp $ */ /* $FreeBSD$ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * HID spec: http://www.usb.org/developers/data/usbhid10.pdf */ #include #include #include #include #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef UHID_DEBUG #define DPRINTF(x) if (uhiddebug) logprintf x #define DPRINTFN(n,x) if (uhiddebug>(n)) logprintf x int uhiddebug = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct uhid_softc { USBBASEDEVICE sc_dev; /* base device */ usbd_interface_handle sc_iface; /* interface */ usbd_pipe_handle sc_intrpipe; /* interrupt pipe */ int sc_ep_addr; int sc_isize; int sc_osize; int sc_fsize; u_int8_t sc_iid; u_int8_t sc_oid; u_int8_t sc_fid; char *sc_ibuf; char *sc_obuf; void *sc_repdesc; int sc_repdesc_size; struct clist sc_q; struct selinfo sc_rsel; u_char sc_state; /* driver state */ #define UHID_OPEN 0x01 /* device is open */ #define UHID_ASLP 0x02 /* waiting for device data */ #define UHID_NEEDCLEAR 0x04 /* needs clearing endpoint stall */ #define UHID_IMMED 0x08 /* return read data immediately */ int sc_refcnt; u_char sc_dying; + +#if defined(__FreeBSD__) + dev_t dev; +#endif }; #define UHIDUNIT(dev) (minor(dev)) #define UHID_CHUNK 128 /* chunk size for read */ #define UHID_BSIZE 1020 /* buffer size */ #if defined(__NetBSD__) || defined(__OpenBSD__) cdev_decl(uhid); #elif defined(__FreeBSD__) d_open_t uhidopen; d_close_t uhidclose; d_read_t uhidread; d_write_t uhidwrite; d_ioctl_t uhidioctl; d_poll_t uhidpoll; #define UHID_CDEV_MAJOR 122 static struct cdevsw uhid_cdevsw = { /* open */ uhidopen, /* close */ uhidclose, /* read */ uhidread, /* write */ uhidwrite, /* ioctl */ uhidioctl, /* poll */ uhidpoll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "uhid", /* maj */ UHID_CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; #endif static void uhid_intr __P((usbd_xfer_handle, usbd_private_handle, usbd_status)); static int uhid_do_read __P((struct uhid_softc *, struct uio *uio, int)); static int uhid_do_write __P((struct uhid_softc *, struct uio *uio, int)); static int uhid_do_ioctl __P((struct uhid_softc *, u_long, caddr_t, int, struct proc *)); USB_DECLARE_DRIVER(uhid); USB_MATCH(uhid) { USB_MATCH_START(uhid, uaa); usb_interface_descriptor_t *id; if (uaa->iface == NULL) return (UMATCH_NONE); id = usbd_get_interface_descriptor(uaa->iface); if (id == NULL || id->bInterfaceClass != UCLASS_HID) return (UMATCH_NONE); return (UMATCH_IFACECLASS_GENERIC); } USB_ATTACH(uhid) { USB_ATTACH_START(uhid, sc, uaa); usbd_interface_handle iface = uaa->iface; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; int size; void *desc; usbd_status err; char devinfo[1024]; sc->sc_iface = iface; id = usbd_get_interface_descriptor(iface); usbd_devinfo(uaa->device, 0, devinfo); USB_ATTACH_SETUP; printf("%s: %s, iclass %d/%d\n", USBDEVNAME(sc->sc_dev), devinfo, id->bInterfaceClass, id->bInterfaceSubClass); ed = usbd_interface2endpoint_descriptor(iface, 0); if (ed == NULL) { printf("%s: could not read endpoint descriptor\n", USBDEVNAME(sc->sc_dev)); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } DPRINTFN(10,("uhid_attach: bLength=%d bDescriptorType=%d " "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" " bInterval=%d\n", ed->bLength, ed->bDescriptorType, ed->bEndpointAddress & UE_ADDR, UE_GET_DIR(ed->bEndpointAddress)==UE_DIR_IN? "in" : "out", ed->bmAttributes & UE_XFERTYPE, UGETW(ed->wMaxPacketSize), ed->bInterval)); if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || (ed->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) { printf("%s: unexpected endpoint\n", USBDEVNAME(sc->sc_dev)); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } sc->sc_ep_addr = ed->bEndpointAddress; desc = 0; err = usbd_alloc_report_desc(uaa->iface, &desc, &size, M_USBDEV); if (err) { printf("%s: no report descriptor\n", USBDEVNAME(sc->sc_dev)); sc->sc_dying = 1; if (desc != NULL) free(desc, M_USBDEV); USB_ATTACH_ERROR_RETURN; } (void)usbd_set_idle(iface, 0, 0); sc->sc_isize = hid_report_size(desc, size, hid_input, &sc->sc_iid); sc->sc_osize = hid_report_size(desc, size, hid_output, &sc->sc_oid); sc->sc_fsize = hid_report_size(desc, size, hid_feature, &sc->sc_fid); sc->sc_repdesc = desc; sc->sc_repdesc_size = size; -#ifdef __FreeBSD__ - { - static int global_init_done = 0; - - if (!global_init_done) { - cdevsw_add(&uhid_cdevsw); - global_init_done = 1; - } - } +#if defined(__FreeBSD__) + sc->dev = make_dev(&uhid_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, + 0644, "uhid%d", device_get_unit(self)); #endif + USB_ATTACH_SUCCESS_RETURN; } #if defined(__NetBSD__) || defined(__OpenBSD__) int uhid_activate(self, act) device_ptr_t self; enum devact act; { struct uhid_softc *sc = (struct uhid_softc *)self; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); break; case DVACT_DEACTIVATE: sc->sc_dying = 1; break; } return (0); } #endif USB_DETACH(uhid) { USB_DETACH_START(uhid, sc); int s; #if defined(__NetBSD__) || defined(__OpenBSD__) int maj, mn; +#elif defined(__FreeBSD__) + struct vnode *vp; +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) DPRINTF(("uhid_detach: sc=%p flags=%d\n", sc, flags)); #else DPRINTF(("uhid_detach: sc=%p\n", sc)); #endif sc->sc_dying = 1; if (sc->sc_intrpipe != NULL) usbd_abort_pipe(sc->sc_intrpipe); if (sc->sc_state & UHID_OPEN) { s = splusb(); if (--sc->sc_refcnt >= 0) { /* Wake everyone */ wakeup(&sc->sc_q); /* Wait for processes to go away. */ usb_detach_wait(USBDEV(sc->sc_dev)); } splx(s); } #if defined(__NetBSD__) || defined(__OpenBSD__) /* locate the major number */ for (maj = 0; maj < nchrdev; maj++) if (cdevsw[maj].d_open == uhidopen) break; /* Nuke the vnodes for any open instances (calls close). */ mn = self->dv_unit; vdevgone(maj, mn, mn, VCHR); #elif defined(__FreeBSD__) - /* XXX not implemented yet */ + vp = SLIST_FIRST(&sc->dev->si_hlist); + if (vp) + VOP_REVOKE(vp, REVOKEALL); + + destroy_dev(sc->dev); #endif free(sc->sc_repdesc, M_USBDEV); return (0); } void uhid_intr(xfer, addr, status) usbd_xfer_handle xfer; usbd_private_handle addr; usbd_status status; { struct uhid_softc *sc = addr; DPRINTFN(5, ("uhid_intr: status=%d\n", status)); DPRINTFN(5, ("uhid_intr: data = %02x %02x %02x\n", sc->sc_ibuf[0], sc->sc_ibuf[1], sc->sc_ibuf[2])); if (status == USBD_CANCELLED) return; if (status != USBD_NORMAL_COMPLETION) { DPRINTF(("uhid_intr: status=%d\n", status)); sc->sc_state |= UHID_NEEDCLEAR; return; } (void) b_to_q(sc->sc_ibuf, sc->sc_isize, &sc->sc_q); if (sc->sc_state & UHID_ASLP) { sc->sc_state &= ~UHID_ASLP; DPRINTFN(5, ("uhid_intr: waking %p\n", sc)); wakeup(&sc->sc_q); } selwakeup(&sc->sc_rsel); } int uhidopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct uhid_softc *sc; usbd_status err; USB_GET_SC_OPEN(uhid, UHIDUNIT(dev), sc); DPRINTF(("uhidopen: sc=%p\n", sc)); if (sc->sc_dying) return (ENXIO); if (sc->sc_state & UHID_OPEN) return (EBUSY); sc->sc_state |= UHID_OPEN; if (clalloc(&sc->sc_q, UHID_BSIZE, UHID_BSIZE) == -1) { sc->sc_state &= ~UHID_OPEN; return (ENOMEM); } sc->sc_ibuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); sc->sc_obuf = malloc(sc->sc_osize, M_USBDEV, M_WAITOK); /* Set up interrupt pipe. */ err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr, USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc, sc->sc_ibuf, sc->sc_isize, uhid_intr); if (err) { DPRINTF(("uhidopen: usbd_open_pipe_intr failed, " "error=%d\n",err)); free(sc->sc_ibuf, M_USBDEV); free(sc->sc_obuf, M_USBDEV); sc->sc_state &= ~UHID_OPEN; return (EIO); } sc->sc_state &= ~UHID_IMMED; return (0); } int uhidclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct uhid_softc *sc; USB_GET_SC(uhid, UHIDUNIT(dev), sc); DPRINTF(("uhidclose: sc=%p\n", sc)); /* Disable interrupts. */ usbd_abort_pipe(sc->sc_intrpipe); usbd_close_pipe(sc->sc_intrpipe); sc->sc_intrpipe = 0; clfree(&sc->sc_q); free(sc->sc_ibuf, M_USBDEV); free(sc->sc_obuf, M_USBDEV); sc->sc_state &= ~UHID_OPEN; return (0); } int uhid_do_read(sc, uio, flag) struct uhid_softc *sc; struct uio *uio; int flag; { int s; int error = 0; size_t length; u_char buffer[UHID_CHUNK]; usbd_status err; DPRINTFN(1, ("uhidread\n")); if (sc->sc_state & UHID_IMMED) { DPRINTFN(1, ("uhidread immed\n")); err = usbd_get_report(sc->sc_iface, UHID_INPUT_REPORT, sc->sc_iid, buffer, sc->sc_isize); if (err) return (EIO); return (uiomove(buffer, sc->sc_isize, uio)); } s = splusb(); while (sc->sc_q.c_cc == 0) { if (flag & IO_NDELAY) { splx(s); return (EWOULDBLOCK); } sc->sc_state |= UHID_ASLP; DPRINTFN(5, ("uhidread: sleep on %p\n", sc)); error = tsleep(&sc->sc_q, PZERO | PCATCH, "uhidrea", 0); DPRINTFN(5, ("uhidread: woke, error=%d\n", error)); if (sc->sc_dying) error = EIO; if (error) { sc->sc_state &= ~UHID_ASLP; break; } if (sc->sc_state & UHID_NEEDCLEAR) { DPRINTFN(-1,("uhidread: clearing stall\n")); sc->sc_state &= ~UHID_NEEDCLEAR; usbd_clear_endpoint_stall(sc->sc_intrpipe); } } splx(s); /* Transfer as many chunks as possible. */ while (sc->sc_q.c_cc > 0 && uio->uio_resid > 0 && !error) { length = min(sc->sc_q.c_cc, uio->uio_resid); if (length > sizeof(buffer)) length = sizeof(buffer); /* Remove a small chunk from the input queue. */ (void) q_to_b(&sc->sc_q, buffer, length); DPRINTFN(5, ("uhidread: got %d chars\n", length)); /* Copy the data to the user process. */ if ((error = uiomove(buffer, length, uio)) != 0) break; } return (error); } int uhidread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct uhid_softc *sc; int error; USB_GET_SC(uhid, UHIDUNIT(dev), sc); sc->sc_refcnt++; error = uhid_do_read(sc, uio, flag); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } int uhid_do_write(sc, uio, flag) struct uhid_softc *sc; struct uio *uio; int flag; { int error; int size; usbd_status err; DPRINTFN(1, ("uhidwrite\n")); if (sc->sc_dying) return (EIO); size = sc->sc_osize; error = 0; if (uio->uio_resid != size) return (EINVAL); error = uiomove(sc->sc_obuf, size, uio); if (!error) { if (sc->sc_oid) err = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, sc->sc_obuf[0], sc->sc_obuf+1, size-1); else err = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, 0, sc->sc_obuf, size); if (err) error = EIO; } return (error); } int uhidwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct uhid_softc *sc; int error; USB_GET_SC(uhid, UHIDUNIT(dev), sc); sc->sc_refcnt++; error = uhid_do_write(sc, uio, flag); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } int uhid_do_ioctl(sc, cmd, addr, flag, p) struct uhid_softc *sc; u_long cmd; caddr_t addr; int flag; struct proc *p; { struct usb_ctl_report_desc *rd; struct usb_ctl_report *re; int size, id; usbd_status err; DPRINTFN(2, ("uhidioctl: cmd=%lx\n", cmd)); if (sc->sc_dying) return (EIO); switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ break; case USB_GET_REPORT_DESC: rd = (struct usb_ctl_report_desc *)addr; size = min(sc->sc_repdesc_size, sizeof rd->data); rd->size = size; memcpy(rd->data, sc->sc_repdesc, size); break; case USB_SET_IMMED: if (*(int *)addr) { /* XXX should read into ibuf, but does it matter? */ err = usbd_get_report(sc->sc_iface, UHID_INPUT_REPORT, sc->sc_iid, sc->sc_ibuf, sc->sc_isize); if (err) return (EOPNOTSUPP); sc->sc_state |= UHID_IMMED; } else sc->sc_state &= ~UHID_IMMED; break; case USB_GET_REPORT: re = (struct usb_ctl_report *)addr; switch (re->report) { case UHID_INPUT_REPORT: size = sc->sc_isize; id = sc->sc_iid; break; case UHID_OUTPUT_REPORT: size = sc->sc_osize; id = sc->sc_oid; break; case UHID_FEATURE_REPORT: size = sc->sc_fsize; id = sc->sc_fid; break; default: return (EINVAL); } err = usbd_get_report(sc->sc_iface, re->report, id, re->data, size); if (err) return (EIO); break; default: return (EINVAL); } return (0); } int uhidioctl(dev, cmd, addr, flag, p) dev_t dev; u_long cmd; caddr_t addr; int flag; struct proc *p; { struct uhid_softc *sc; int error; USB_GET_SC(uhid, UHIDUNIT(dev), sc); sc->sc_refcnt++; error = uhid_do_ioctl(sc, cmd, addr, flag, p); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } int uhidpoll(dev, events, p) dev_t dev; int events; struct proc *p; { struct uhid_softc *sc; int revents = 0; int s; USB_GET_SC(uhid, UHIDUNIT(dev), sc); if (sc->sc_dying) return (EIO); s = splusb(); if (events & (POLLOUT | POLLWRNORM)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM)) { if (sc->sc_q.c_cc > 0) revents |= events & (POLLIN | POLLRDNORM); else selrecord(p, &sc->sc_rsel); } splx(s); return (revents); } #if defined(__FreeBSD__) DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, usbd_driver_load, 0); #endif Index: head/sys/dev/usb/ulpt.c =================================================================== --- head/sys/dev/usb/ulpt.c (revision 53513) +++ head/sys/dev/usb/ulpt.c (revision 53514) @@ -1,589 +1,598 @@ /* $NetBSD: ulpt.c,v 1.27 1999/10/13 08:10:57 augustss Exp $ */ /* $FreeBSD$ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Printer Class spec: http://www.usb.org/developers/data/usbprn10.pdf */ /* XXX Note in the manpage the ULPT_NOPRIME version of the printer */ #include #include #include #include #if defined(__NetBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #endif #include #include #include #include #include #include #include #include #include #define TIMEOUT hz*16 /* wait up to 16 seconds for a ready */ #define STEP hz/4 #define LPTPRI (PZERO+8) #define ULPT_BSIZE 16384 #ifdef ULPT_DEBUG #define DPRINTF(x) if (ulptdebug) logprintf x #define DPRINTFN(n,x) if (ulptdebug>(n)) logprintf x int ulptdebug = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define UR_GET_DEVICE_ID 0 #define UR_GET_PORT_STATUS 1 #define UR_SOFT_RESET 2 #define LPS_NERR 0x08 /* printer no error */ #define LPS_SELECT 0x10 /* printer selected */ #define LPS_NOPAPER 0x20 /* printer out of paper */ #define LPS_INVERT (LPS_SELECT|LPS_NERR) #define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) struct ulpt_softc { USBBASEDEVICE sc_dev; usbd_device_handle sc_udev; /* device */ usbd_interface_handle sc_iface; /* interface */ int sc_ifaceno; usbd_pipe_handle sc_bulkpipe; /* bulk pipe */ int sc_bulk; u_char sc_state; #define ULPT_OPEN 0x01 /* device is open */ #define ULPT_OBUSY 0x02 /* printer is busy doing output */ #define ULPT_INIT 0x04 /* waiting to initialize for open */ u_char sc_flags; #define ULPT_NOPRIME 0x40 /* don't prime on open */ u_char sc_laststatus; int sc_refcnt; u_char sc_dying; #if defined(__FreeBSD__) dev_t dev; dev_t dev_noprime; #endif }; #if defined(__NetBSD__) || defined(__OpenBSD__) cdev_decl(ulpt); #elif defined(__FreeBSD__) static d_open_t ulptopen; static d_close_t ulptclose; static d_write_t ulptwrite; static d_ioctl_t ulptioctl; #define ULPT_CDEV_MAJOR 113 static struct cdevsw ulpt_cdevsw = { /* open */ ulptopen, /* close */ ulptclose, /* read */ noread, /* write */ ulptwrite, /* ioctl */ ulptioctl, /* poll */ nopoll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "ulpt", /* maj */ ULPT_CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; #endif void ulpt_disco __P((void *)); int ulpt_do_write __P((struct ulpt_softc *, struct uio *uio, int)); int ulpt_status __P((struct ulpt_softc *)); void ulpt_reset __P((struct ulpt_softc *)); int ulpt_statusmsg __P((u_char, struct ulpt_softc *)); void ieee1284_print_id __P((char *)); #define ULPTUNIT(s) (minor(s) & 0x1f) #define ULPTFLAGS(s) (minor(s) & 0xe0) USB_DECLARE_DRIVER(ulpt); USB_MATCH(ulpt) { USB_MATCH_START(ulpt, uaa); usb_interface_descriptor_t *id; DPRINTFN(10,("ulpt_match\n")); if (uaa->iface == NULL) return (UMATCH_NONE); id = usbd_get_interface_descriptor(uaa->iface); if (id != NULL && id->bInterfaceClass == UCLASS_PRINTER && id->bInterfaceSubClass == USUBCLASS_PRINTER && (id->bInterfaceProtocol == UPROTO_PRINTER_UNI || id->bInterfaceProtocol == UPROTO_PRINTER_BI)) return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); return (UMATCH_NONE); } USB_ATTACH(ulpt) { USB_ATTACH_START(ulpt, sc, uaa); usbd_device_handle dev = uaa->device; usbd_interface_handle iface = uaa->iface; usb_interface_descriptor_t *id = usbd_get_interface_descriptor(iface); char devinfo[1024]; usb_endpoint_descriptor_t *ed; usbd_status err; DPRINTFN(10,("ulpt_attach: sc=%p\n", sc)); usbd_devinfo(dev, 0, devinfo); USB_ATTACH_SETUP; printf("%s: %s, iclass %d/%d\n", USBDEVNAME(sc->sc_dev), devinfo, id->bInterfaceClass, id->bInterfaceSubClass); /* Figure out which endpoint is the bulk out endpoint. */ ed = usbd_interface2endpoint_descriptor(iface, 0); if (ed == NULL) goto nobulk; if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_OUT || (ed->bmAttributes & UE_XFERTYPE) != UE_BULK) { /* In case we are using a bidir protocol... */ ed = usbd_interface2endpoint_descriptor(iface, 1); if (ed == NULL) goto nobulk; if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_OUT || (ed->bmAttributes & UE_XFERTYPE) != UE_BULK) goto nobulk; } sc->sc_bulk = ed->bEndpointAddress; DPRINTFN(10, ("ulpt_attach: bulk=%d\n", sc->sc_bulk)); sc->sc_iface = iface; err = usbd_interface2device_handle(iface, &sc->sc_udev); if (err) { sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } sc->sc_ifaceno = id->bInterfaceNumber; #if 0 /* * This code is disabled because for some mysterious it causes * printing not to work. But only sometimes, and mostly with * UHCI and less often with OHCI. *sigh* */ { usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); usb_device_request_t req; int len, alen; req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_DEVICE_ID; USETW(req.wValue, cd->bConfigurationValue); USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); USETW(req.wLength, sizeof devinfo - 1); err = usbd_do_request_flags(dev, &req, devinfo, USBD_SHORT_XFER_OK, &alen); if (err) { printf("%s: cannot get device id\n", USBDEVNAME(sc->sc_dev)); } else if (alen <= 2) { printf("%s: empty device id, no printer connected?\n", USBDEVNAME(sc->sc_dev)); } else { /* devinfo now contains an IEEE-1284 device ID */ len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); if (len > sizeof devinfo - 3) len = sizeof devinfo - 3; devinfo[len] = 0; printf("%s: device id <", USBDEVNAME(sc->sc_dev)); ieee1284_print_id(devinfo+2); printf(">\n"); } } #endif #if defined(__FreeBSD__) sc->dev = make_dev(&ulpt_cdevsw, device_get_unit(self), UID_ROOT, GID_OPERATOR, 0644, "ulpt%d", device_get_unit(self)); sc->dev_noprime = make_dev(&ulpt_cdevsw, device_get_unit(self)|ULPT_NOPRIME, UID_ROOT, GID_OPERATOR, 0644, "unlpt%d", device_get_unit(self)); #endif USB_ATTACH_SUCCESS_RETURN; nobulk: printf("%s: could not find bulk endpoint\n", USBDEVNAME(sc->sc_dev)); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } #if defined(__NetBSD__) || defined(__OpenBSD__) int ulpt_activate(self, act) device_ptr_t self; enum devact act; { struct ulpt_softc *sc = (struct ulpt_softc *)self; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); break; case DVACT_DEACTIVATE: sc->sc_dying = 1; break; } return (0); } #endif USB_DETACH(ulpt) { USB_DETACH_START(ulpt, sc); int s; #if defined(__NetBSD__) || defined(__OpenBSD__) int maj, mn; +#elif defined(__FreeBSD__) + struct vnode *vp; +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) DPRINTF(("ulpt_detach: sc=%p flags=%d\n", sc, flags)); #elif defined(__FreeBSD__) DPRINTF(("ulpt_detach: sc=%p\n", sc)); #endif sc->sc_dying = 1; if (sc->sc_bulkpipe != NULL) usbd_abort_pipe(sc->sc_bulkpipe); s = splusb(); if (--sc->sc_refcnt >= 0) { /* There is noone to wake, aborting the pipe is enough */ /* Wait for processes to go away. */ usb_detach_wait(USBDEV(sc->sc_dev)); } splx(s); #if defined(__NetBSD__) || defined(__OpenBSD__) /* locate the major number */ for (maj = 0; maj < nchrdev; maj++) if (cdevsw[maj].d_open == ulptopen) break; /* Nuke the vnodes for any open instances (calls close). */ mn = self->dv_unit; vdevgone(maj, mn, mn, VCHR); #elif defined(__FreeBSD__) - /* XXX not implemented yet */ + vp = SLIST_FIRST(&sc->dev->si_hlist); + if (vp) + VOP_REVOKE(vp, REVOKEALL); + vp = SLIST_FIRST(&sc->dev_noprime->si_hlist); + if (vp) + VOP_REVOKE(vp, REVOKEALL); destroy_dev(sc->dev); destroy_dev(sc->dev_noprime); #endif return (0); } int ulpt_status(sc) struct ulpt_softc *sc; { usb_device_request_t req; usbd_status err; u_char status; req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_PORT_STATUS; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_ifaceno); USETW(req.wLength, 1); err = usbd_do_request(sc->sc_udev, &req, &status); DPRINTFN(1, ("ulpt_status: status=0x%02x err=%d\n", status, err)); if (!err) return (status); else return (0); } void ulpt_reset(sc) struct ulpt_softc *sc; { usb_device_request_t req; DPRINTFN(1, ("ulpt_reset\n")); req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_SOFT_RESET; USETW(req.wValue, 0); USETW(req.wIndex, sc->sc_ifaceno); USETW(req.wLength, 0); (void)usbd_do_request(sc->sc_udev, &req, 0); } /* * Reset the printer, then wait until it's selected and not busy. */ int ulptopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { u_char flags = ULPTFLAGS(dev); struct ulpt_softc *sc; usbd_status err; int spin, error; USB_GET_SC_OPEN(ulpt, ULPTUNIT(dev), sc); if (sc == NULL || sc->sc_iface == NULL || sc->sc_dying) return (ENXIO); if (sc->sc_state) return (EBUSY); sc->sc_state = ULPT_INIT; sc->sc_flags = flags; DPRINTF(("ulptopen: flags=0x%x\n", (unsigned)flags)); #if defined(ULPT_DEBUG) && defined(__FreeBSD__) /* Ignoring these flags might not be a good idea */ if ((flags & ~ULPT_NOPRIME) != 0) printf("ulptopen: flags ignored: %b\n", flags, "\20\3POS_INIT\4POS_ACK\6PRIME_OPEN\7AUTOLF\10BYPASS"); #endif if ((flags & ULPT_NOPRIME) == 0) ulpt_reset(sc); for (spin = 0; (ulpt_status(sc) & LPS_SELECT) == 0; spin += STEP) { if (spin >= TIMEOUT) { sc->sc_state = 0; return (EBUSY); } /* wait 1/4 second, give up if we get a signal */ error = tsleep((caddr_t)sc, LPTPRI | PCATCH, "ulptop", STEP); if (error != EWOULDBLOCK) { sc->sc_state = 0; return (error); } } err = usbd_open_pipe(sc->sc_iface, sc->sc_bulk, 0, &sc->sc_bulkpipe); if (err) { sc->sc_state = 0; return (EIO); } sc->sc_state = ULPT_OPEN; DPRINTF(("ulptopen: done\n")); return (0); } int ulpt_statusmsg(status, sc) u_char status; struct ulpt_softc *sc; { u_char new; status = (status ^ LPS_INVERT) & LPS_MASK; new = status & ~sc->sc_laststatus; sc->sc_laststatus = status; if (new & LPS_SELECT) log(LOG_NOTICE, "%s: offline\n", USBDEVNAME(sc->sc_dev)); else if (new & LPS_NOPAPER) log(LOG_NOTICE, "%s: out of paper\n", USBDEVNAME(sc->sc_dev)); else if (new & LPS_NERR) log(LOG_NOTICE, "%s: output error\n", USBDEVNAME(sc->sc_dev)); return (status); } int ulptclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct ulpt_softc *sc; USB_GET_SC(ulpt, ULPTUNIT(dev), sc); if (sc->sc_state != ULPT_OPEN) /* We are being forced to close before the open completed. */ return (0); usbd_close_pipe(sc->sc_bulkpipe); sc->sc_bulkpipe = 0; sc->sc_state = 0; DPRINTF(("ulptclose: closed\n")); return (0); } int ulpt_do_write(sc, uio, flags) struct ulpt_softc *sc; struct uio *uio; int flags; { u_int32_t n; int error = 0; void *bufp; usbd_xfer_handle xfer; usbd_status err; DPRINTF(("ulptwrite\n")); xfer = usbd_alloc_request(sc->sc_udev); if (xfer == NULL) return (ENOMEM); bufp = usbd_alloc_buffer(xfer, ULPT_BSIZE); if (bufp == NULL) { usbd_free_request(xfer); return (ENOMEM); } while ((n = min(ULPT_BSIZE, uio->uio_resid)) != 0) { ulpt_statusmsg(ulpt_status(sc), sc); error = uiomove(bufp, n, uio); if (error) break; DPRINTFN(1, ("ulptwrite: transfer %d bytes\n", n)); err = usbd_bulk_transfer(xfer, sc->sc_bulkpipe, USBD_NO_COPY, USBD_NO_TIMEOUT, bufp, &n, "ulptwr"); if (err) { DPRINTF(("ulptwrite: error=%d\n", err)); error = EIO; break; } } usbd_free_request(xfer); return (error); } int ulptwrite(dev, uio, flags) dev_t dev; struct uio *uio; int flags; { struct ulpt_softc *sc; int error; USB_GET_SC(ulpt, ULPTUNIT(dev), sc); if (sc->sc_dying) return (EIO); sc->sc_refcnt++; error = ulpt_do_write(sc, uio, flags); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } int ulptioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { int error = 0; switch (cmd) { default: error = ENODEV; } return (error); } #if 0 /* XXX This does not belong here. */ /* * Print select parts of a IEEE 1284 device ID. */ void ieee1284_print_id(str) char *str; { char *p, *q; for (p = str-1; p; p = strchr(p, ';')) { p++; /* skip ';' */ if (strncmp(p, "MFG:", 4) == 0 || strncmp(p, "MANUFACTURER:", 14) == 0 || strncmp(p, "MDL:", 4) == 0 || strncmp(p, "MODEL:", 6) == 0) { q = strchr(p, ';'); if (q) printf("%.*s", (int)(q - p + 1), p); } } } #endif #if defined(__FreeBSD__) DRIVER_MODULE(ulpt, uhub, ulpt_driver, ulpt_devclass, usbd_driver_load, 0); #endif Index: head/sys/dev/usb/ums.c =================================================================== --- head/sys/dev/usb/ums.c (revision 53513) +++ head/sys/dev/usb/ums.c (revision 53514) @@ -1,819 +1,821 @@ /* $FreeBSD$ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * HID spec: http://www.usb.org/developers/data/usbhid10.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef UMS_DEBUG #define DPRINTF(x) if (umsdebug) logprintf x #define DPRINTFN(n,x) if (umsdebug>(n)) logprintf x int umsdebug = 1; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define UMSUNIT(s) (minor(s)&0x1f) #define MS_TO_TICKS(ms) ((ms) * hz / 1000) #define QUEUE_BUFSIZE 400 /* MUST be divisible by 5 _and_ 8 */ struct ums_softc { device_t sc_dev; /* base device */ usbd_interface_handle sc_iface; /* interface */ usbd_pipe_handle sc_intrpipe; /* interrupt pipe */ int sc_ep_addr; u_char *sc_ibuf; u_int8_t sc_iid; int sc_isize; struct hid_location sc_loc_x, sc_loc_y, sc_loc_z; struct hid_location *sc_loc_btn; struct callout_handle timeout_handle; /* for spurious button ups */ int sc_enabled; int sc_disconnected; /* device is gone */ int flags; /* device configuration */ #define UMS_Z 0x01 /* z direction available */ #define UMS_SPUR_BUT_UP 0x02 /* spurious button up events */ int nbuttons; #define MAX_BUTTONS 7 /* chosen because sc_buttons is u_char */ u_char qbuf[QUEUE_BUFSIZE]; /* must be divisable by 3&4 */ u_char dummy[100]; /* XXX just for safety and for now */ int qcount, qhead, qtail; mousehw_t hw; mousemode_t mode; mousestatus_t status; int state; # define UMS_ASLEEP 0x01 /* readFromDevice is waiting */ # define UMS_SELECT 0x02 /* select is waiting */ struct selinfo rsel; /* process waiting in select */ dev_t dev; /* specfs */ }; #define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) #define MOUSE_FLAGS (HIO_RELATIVE) static void ums_intr __P((usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)); static void ums_add_to_queue __P((struct ums_softc *sc, int dx, int dy, int dz, int buttons)); static void ums_add_to_queue_timeout __P((void *priv)); static int ums_enable __P((void *)); static void ums_disable __P((void *)); static d_open_t ums_open; static d_close_t ums_close; static d_read_t ums_read; static d_ioctl_t ums_ioctl; static d_poll_t ums_poll; #define UMS_CDEV_MAJOR 111 static struct cdevsw ums_cdevsw = { /* open */ ums_open, /* close */ ums_close, /* read */ ums_read, /* write */ nowrite, /* ioctl */ ums_ioctl, /* poll */ ums_poll, /* mmap */ nommap, /* strategy */ nostrategy, /* name */ "ums", /* maj */ UMS_CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, /* flags */ 0, /* bmaj */ -1 }; USB_DECLARE_DRIVER(ums); USB_MATCH(ums) { USB_MATCH_START(ums, uaa); usb_interface_descriptor_t *id; int size, ret; void *desc; usbd_status err; if (!uaa->iface) return (UMATCH_NONE); id = usbd_get_interface_descriptor(uaa->iface); if (!id || id->bInterfaceClass != UCLASS_HID) return (UMATCH_NONE); err = usbd_alloc_report_desc(uaa->iface, &desc, &size, M_TEMP); if (err) return (UMATCH_NONE); if (hid_is_collection(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) ret = UMATCH_IFACECLASS; else ret = UMATCH_NONE; free(desc, M_TEMP); return (ret); } USB_ATTACH(ums) { USB_ATTACH_START(ums, sc, uaa); usbd_interface_handle iface = uaa->iface; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; int size; void *desc; usbd_status err; char devinfo[1024]; u_int32_t flags; int i; struct hid_location loc_btn; sc->sc_disconnected = 1; sc->sc_iface = iface; id = usbd_get_interface_descriptor(iface); usbd_devinfo(uaa->device, 0, devinfo); USB_ATTACH_SETUP; printf("%s: %s, iclass %d/%d\n", USBDEVNAME(sc->sc_dev), devinfo, id->bInterfaceClass, id->bInterfaceSubClass); ed = usbd_interface2endpoint_descriptor(iface, 0); if (!ed) { printf("%s: could not read endpoint descriptor\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } DPRINTFN(10,("ums_attach: bLength=%d bDescriptorType=%d " "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" " bInterval=%d\n", ed->bLength, ed->bDescriptorType, UE_GET_ADDR(ed->bEndpointAddress), UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out", UE_GET_XFERTYPE(ed->bmAttributes), UGETW(ed->wMaxPacketSize), ed->bInterval)); if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { printf("%s: unexpected endpoint\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } err = usbd_alloc_report_desc(uaa->iface, &desc, &size, M_TEMP); if (err) USB_ATTACH_ERROR_RETURN; if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), hid_input, &sc->sc_loc_x, &flags)) { printf("%s: mouse has no X report\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { printf("%s: X report 0x%04x not supported\n", USBDEVNAME(sc->sc_dev), flags); USB_ATTACH_ERROR_RETURN; } if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), hid_input, &sc->sc_loc_y, &flags)) { printf("%s: mouse has no Y report\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { printf("%s: Y report 0x%04x not supported\n", USBDEVNAME(sc->sc_dev), flags); USB_ATTACH_ERROR_RETURN; } /* try to guess the Z activator: first check Z, then WHEEL */ if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), hid_input, &sc->sc_loc_z, &flags) || hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), hid_input, &sc->sc_loc_z, &flags)) { if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { sc->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ } else { sc->flags |= UMS_Z; } } /* figure out the number of buttons */ for (i = 1; i <= MAX_BUTTONS; i++) if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), hid_input, &loc_btn, 0)) break; sc->nbuttons = i - 1; sc->sc_loc_btn = malloc(sizeof(struct hid_location)*sc->nbuttons, M_USBDEV, M_NOWAIT); if (!sc->sc_loc_btn) { printf("%s: no memory\n", USBDEVNAME(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } printf("%s: %d buttons%s\n", USBDEVNAME(sc->sc_dev), sc->nbuttons, sc->flags & UMS_Z? " and Z dir." : ""); for (i = 1; i <= sc->nbuttons; i++) hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), hid_input, &sc->sc_loc_btn[i-1], 0); sc->sc_isize = hid_report_size(desc, size, hid_input, &sc->sc_iid); sc->sc_ibuf = malloc(sc->sc_isize, M_USB, M_NOWAIT); if (!sc->sc_ibuf) { printf("%s: no memory\n", USBDEVNAME(sc->sc_dev)); free(sc->sc_loc_btn, M_USB); USB_ATTACH_ERROR_RETURN; } sc->sc_ep_addr = ed->bEndpointAddress; sc->sc_disconnected = 0; free(desc, M_TEMP); #ifdef UMS_DEBUG DPRINTF(("ums_attach: sc=%p\n", sc)); DPRINTF(("ums_attach: X\t%d/%d\n", sc->sc_loc_x.pos, sc->sc_loc_x.size)); DPRINTF(("ums_attach: Y\t%d/%d\n", sc->sc_loc_y.pos, sc->sc_loc_y.size)); if (sc->flags & UMS_Z) DPRINTF(("ums_attach: Z\t%d/%d\n", sc->sc_loc_z.pos, sc->sc_loc_z.size)); for (i = 1; i <= sc->nbuttons; i++) { DPRINTF(("ums_attach: B%d\t%d/%d\n", i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size)); } DPRINTF(("ums_attach: size=%d, id=%d\n", sc->sc_isize, sc->sc_iid)); #endif if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) sc->hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->hw.buttons = sc->nbuttons; sc->hw.iftype = MOUSE_IF_USB; sc->hw.type = MOUSE_MOUSE; sc->hw.model = MOUSE_MODEL_GENERIC; sc->hw.hwid = 0; sc->mode.protocol = MOUSE_PROTO_MSC; sc->mode.rate = -1; sc->mode.resolution = MOUSE_RES_UNKNOWN; sc->mode.accelfactor = 0; sc->mode.level = 0; sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->mode.syncmask[1] = MOUSE_MSC_SYNC; sc->status.flags = 0; sc->status.button = sc->status.obutton = 0; sc->status.dx = sc->status.dy = sc->status.dz = 0; sc->rsel.si_flags = 0; sc->rsel.si_pid = 0; sc->dev = make_dev(&ums_cdevsw, device_get_unit(self), UID_ROOT, GID_OPERATOR, 0644, "ums%d", device_get_unit(self)); if (usbd_get_quirks(uaa->device)->uq_flags & UQ_SPUR_BUT_UP) { DPRINTF(("%s: Spurious button up events\n", USBDEVNAME(sc->sc_dev))); sc->flags |= UMS_SPUR_BUT_UP; } USB_ATTACH_SUCCESS_RETURN; } static int ums_detach(device_t self) { struct ums_softc *sc = device_get_softc(self); + struct vnode *vp; if (sc->sc_enabled) ums_disable(sc); DPRINTF(("%s: disconnected\n", USBDEVNAME(self))); free(sc->sc_loc_btn, M_USB); free(sc->sc_ibuf, M_USB); + vp = SLIST_FIRST(&sc->dev->si_hlist); + if (vp) + VOP_REVOKE(vp, REVOKEALL); + + /* someone waiting for data */ /* * XXX If we wakeup the process here, the device will be gone by * the time the process gets a chance to notice. *_close and friends * should be fixed to handle this case. * Or we should do a delayed detach for this. * Does this delay now force tsleep to exit with an error? */ - -#if 0 - /* someone waiting for data */ if (sc->state & UMS_ASLEEP) { sc->state &= ~UMS_ASLEEP; wakeup(sc); } if (sc->state & UMS_SELECT) { sc->state &= ~UMS_SELECT; selwakeup(&sc->rsel); } -#endif destroy_dev(sc->dev); return 0; } void ums_intr(xfer, addr, status) usbd_xfer_handle xfer; usbd_private_handle addr; usbd_status status; { struct ums_softc *sc = addr; u_char *ibuf; int dx, dy, dz; u_char buttons = 0; int i; #define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) DPRINTFN(5, ("ums_intr: sc=%p status=%d\n", sc, status)); DPRINTFN(5, ("ums_intr: data = %02x %02x %02x\n", sc->sc_ibuf[0], sc->sc_ibuf[1], sc->sc_ibuf[2])); if (status == USBD_CANCELLED) return; if (status != USBD_NORMAL_COMPLETION) { DPRINTF(("ums_intr: status=%d\n", status)); usbd_clear_endpoint_stall_async(sc->sc_intrpipe); return; } ibuf = sc->sc_ibuf; if (sc->sc_iid) { if (*ibuf++ != sc->sc_iid) return; } dx = hid_get_data(ibuf, &sc->sc_loc_x); dy = -hid_get_data(ibuf, &sc->sc_loc_y); dz = -hid_get_data(ibuf, &sc->sc_loc_z); for (i = 0; i < sc->nbuttons; i++) if (hid_get_data(ibuf, &sc->sc_loc_btn[i])) buttons |= (1 << UMS_BUT(i)); if (dx || dy || dz || (sc->flags & UMS_Z) || buttons != sc->status.button) { DPRINTFN(5, ("ums_intr: x:%d y:%d z:%d buttons:0x%x\n", dx, dy, dz, buttons)); sc->status.button = buttons; sc->status.dx += dx; sc->status.dy += dy; sc->status.dz += dz; /* Discard data in case of full buffer */ if (sc->qcount == sizeof(sc->qbuf)) { DPRINTF(("Buffer full, discarded packet")); return; } /* * The Qtronix keyboard has a built in PS/2 port for a mouse. * The firmware once in a while posts a spurious button up * event. This event we ignore by doing a timeout for 50 msecs. * If we receive dx=dy=dz=buttons=0 before we add the event to * the queue. * In any other case we delete the timeout event. */ if (sc->flags & UMS_SPUR_BUT_UP && dx == 0 && dy == 0 && dz == 0 && buttons == 0) { usb_timeout(ums_add_to_queue_timeout, (void *) sc, MS_TO_TICKS(50 /*msecs*/), sc->timeout_handle); } else { usb_untimeout(ums_add_to_queue_timeout, (void *) sc, sc->timeout_handle); ums_add_to_queue(sc, dx, dy, dz, buttons); } } } static void ums_add_to_queue_timeout(void *priv) { struct ums_softc *sc = priv; int s; s = splusb(); ums_add_to_queue(sc, 0, 0, 0, 0); splx(s); } static void ums_add_to_queue(struct ums_softc *sc, int dx, int dy, int dz, int buttons) { /* Discard data in case of full buffer */ if (sc->qhead+sc->mode.packetsize > sizeof(sc->qbuf)) { DPRINTF(("Buffer full, discarded packet")); return; } if (dx > 254) dx = 254; if (dx < -256) dx = -256; if (dy > 254) dy = 254; if (dy < -256) dy = -256; if (dz > 126) dz = 126; if (dz < -128) dz = -128; sc->qbuf[sc->qhead] = sc->mode.syncmask[1]; sc->qbuf[sc->qhead] |= ~buttons & MOUSE_MSC_BUTTONS; sc->qbuf[sc->qhead+1] = dx >> 1; sc->qbuf[sc->qhead+2] = dy >> 1; sc->qbuf[sc->qhead+3] = dx - (dx >> 1); sc->qbuf[sc->qhead+4] = dy - (dy >> 1); if (sc->mode.level == 1) { sc->qbuf[sc->qhead+5] = dz >> 1; sc->qbuf[sc->qhead+6] = dz - (dz >> 1); sc->qbuf[sc->qhead+7] = ((~buttons >> 3) & MOUSE_SYS_EXTBUTTONS); } sc->qhead += sc->mode.packetsize; sc->qcount += sc->mode.packetsize; /* wrap round at end of buffer */ if (sc->qhead >= sizeof(sc->qbuf)) sc->qhead = 0; /* someone waiting for data */ if (sc->state & UMS_ASLEEP) { sc->state &= ~UMS_ASLEEP; wakeup(sc); } if (sc->state & UMS_SELECT) { sc->state &= ~UMS_SELECT; selwakeup(&sc->rsel); } } static int ums_enable(v) void *v; { struct ums_softc *sc = v; usbd_status err; if (sc->sc_enabled) return EBUSY; sc->sc_enabled = 1; sc->qcount = 0; sc->qhead = sc->qtail = 0; sc->status.flags = 0; sc->status.button = sc->status.obutton = 0; sc->status.dx = sc->status.dy = sc->status.dz = 0; callout_handle_init(&sc->timeout_handle); /* Set up interrupt pipe. */ err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr, USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc, sc->sc_ibuf, sc->sc_isize, ums_intr); if (err) { DPRINTF(("ums_enable: usbd_open_pipe_intr failed, error=%d\n", err)); sc->sc_enabled = 0; return (EIO); } return (0); } static void ums_disable(priv) void *priv; { struct ums_softc *sc = priv; usb_untimeout(ums_add_to_queue_timeout, sc, sc->timeout_handle); /* Disable interrupts. */ usbd_abort_pipe(sc->sc_intrpipe); usbd_close_pipe(sc->sc_intrpipe); sc->sc_enabled = 0; if (sc->qcount != 0) DPRINTF(("Discarded %d bytes in queue\n", sc->qcount)); } static int ums_open(dev_t dev, int flag, int fmt, struct proc *p) { struct ums_softc *sc; USB_GET_SC_OPEN(ums, UMSUNIT(dev), sc); return ums_enable(sc); } static int ums_close(dev_t dev, int flag, int fmt, struct proc *p) { struct ums_softc *sc; USB_GET_SC(ums, UMSUNIT(dev), sc); if (!sc) return 0; if (sc->sc_enabled) ums_disable(sc); return 0; } static int ums_read(dev_t dev, struct uio *uio, int flag) { struct ums_softc *sc; int s; char buf[sizeof(sc->qbuf)]; int l = 0; int error; USB_GET_SC(ums, UMSUNIT(dev), sc); s = splusb(); if (!sc) { splx(s); return EIO; } while (sc->qcount == 0 ) { if (flag & IO_NDELAY) { /* non-blocking I/O */ splx(s); return EWOULDBLOCK; } sc->state |= UMS_ASLEEP; /* blocking I/O */ error = tsleep(sc, PZERO | PCATCH, "umsrea", 0); if (error) { splx(s); return error; } else if (!sc->sc_enabled) { splx(s); return EINTR; } /* check whether the device is still there */ sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); if (!sc) { splx(s); return EIO; } } /* * XXX we could optimise the use of splx/splusb somewhat. The writer * process only extends qcount and qtail. We could copy them and use the copies * to do the copying out of the queue. */ while ((sc->qcount > 0) && (uio->uio_resid > 0)) { l = (sc->qcount < uio->uio_resid? sc->qcount:uio->uio_resid); if (l > sizeof(buf)) l = sizeof(buf); if (l > sizeof(sc->qbuf) - sc->qtail) /* transfer till end of buf */ l = sizeof(sc->qbuf) - sc->qtail; splx(s); uiomove(&sc->qbuf[sc->qtail], l, uio); s = splusb(); if ( sc->qcount - l < 0 ) { DPRINTF(("qcount below 0, count=%d l=%d\n", sc->qcount, l)); sc->qcount = l; } sc->qcount -= l; /* remove the bytes from the buffer */ sc->qtail = (sc->qtail + l) % sizeof(sc->qbuf); } splx(s); return 0; } static int ums_poll(dev_t dev, int events, struct proc *p) { struct ums_softc *sc; int revents = 0; int s; USB_GET_SC(ums, UMSUNIT(dev), sc); if (!sc) return 0; s = splusb(); if (events & (POLLIN | POLLRDNORM)) { if (sc->qcount) { revents = events & (POLLIN | POLLRDNORM); } else { sc->state |= UMS_SELECT; selrecord(p, &sc->rsel); } } splx(s); return revents; } int ums_ioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p) { struct ums_softc *sc; int error = 0; int s; mousemode_t mode; USB_GET_SC(ums, UMSUNIT(dev), sc); if (!sc) return EIO; switch(cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)addr = sc->hw; break; case MOUSE_GETMODE: *(mousemode_t *)addr = sc->mode; break; case MOUSE_SETMODE: mode = *(mousemode_t *)addr; if (mode.level == -1) /* don't change the current setting */ ; else if ((mode.level < 0) || (mode.level > 1)) return (EINVAL); s = splusb(); sc->mode.level = mode.level; if (sc->mode.level == 0) { if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) sc->hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->hw.buttons = sc->nbuttons; sc->mode.protocol = MOUSE_PROTO_MSC; sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->mode.level == 1) { if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) sc->hw.buttons = MOUSE_SYS_MAXBUTTON; else sc->hw.buttons = sc->nbuttons; sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->mode.syncmask[1] = MOUSE_SYS_SYNC; } bzero(sc->qbuf, sizeof(sc->qbuf)); sc->qhead = sc->qtail = sc->qcount = 0; splx(s); break; case MOUSE_GETLEVEL: *(int *)addr = sc->mode.level; break; case MOUSE_SETLEVEL: if (*(int *)addr < 0 || *(int *)addr > 1) return (EINVAL); s = splusb(); sc->mode.level = *(int *)addr; if (sc->mode.level == 0) { if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) sc->hw.buttons = MOUSE_MSC_MAXBUTTON; else sc->hw.buttons = sc->nbuttons; sc->mode.protocol = MOUSE_PROTO_MSC; sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->mode.syncmask[1] = MOUSE_MSC_SYNC; } else if (sc->mode.level == 1) { if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) sc->hw.buttons = MOUSE_SYS_MAXBUTTON; else sc->hw.buttons = sc->nbuttons; sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; sc->mode.syncmask[1] = MOUSE_SYS_SYNC; } bzero(sc->qbuf, sizeof(sc->qbuf)); sc->qhead = sc->qtail = sc->qcount = 0; splx(s); break; case MOUSE_GETSTATUS: { mousestatus_t *status = (mousestatus_t *) addr; s = splusb(); *status = sc->status; sc->status.obutton = sc->status.button; sc->status.button = 0; sc->status.dx = sc->status.dy = sc->status.dz = 0; splx(s); if (status->dx || status->dy || status->dz) status->flags |= MOUSE_POSCHANGED; if (status->button != status->obutton) status->flags |= MOUSE_BUTTONSCHANGED; break; } default: error = ENOTTY; } return error; } DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, usbd_driver_load, 0);