/* $NetBSD: ucycom.c,v 1.57 2024/03/17 20:10:52 jakllsch Exp $ */ /* * Copyright (c) 2005 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Nick Hudson * * 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. * * 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. */ /* * This code is based on the ucom driver. */ /* * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to * RS232 bridges. */ #include __KERNEL_RCSID(0, "$NetBSD: ucycom.c,v 1.57 2024/03/17 20:10:52 jakllsch Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ioconf.h" #ifdef UCYCOM_DEBUG #define DPRINTF(x) if (ucycomdebug) printf x #define DPRINTFN(n, x) if (ucycomdebug > (n)) printf x int ucycomdebug = 20; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define UCYCOMCALLUNIT_MASK TTCALLUNIT_MASK #define UCYCOMUNIT_MASK TTUNIT_MASK #define UCYCOMDIALOUT_MASK TTDIALOUT_MASK #define UCYCOMCALLUNIT(x) TTCALLUNIT(x) #define UCYCOMUNIT(x) TTUNIT(x) #define UCYCOMDIALOUT(x) TTDIALOUT(x) /* Configuration Byte */ #define UCYCOM_RESET 0x80 #define UCYCOM_PARITY_TYPE_MASK 0x20 #define UCYCOM_PARITY_ODD 0x20 #define UCYCOM_PARITY_EVEN 0x00 #define UCYCOM_PARITY_MASK 0x10 #define UCYCOM_PARITY_ON 0x10 #define UCYCOM_PARITY_OFF 0x00 #define UCYCOM_STOP_MASK 0x08 #define UCYCOM_STOP_BITS_2 0x08 #define UCYCOM_STOP_BITS_1 0x00 #define UCYCOM_DATA_MASK 0x03 #define UCYCOM_DATA_BITS_8 0x03 #define UCYCOM_DATA_BITS_7 0x02 #define UCYCOM_DATA_BITS_6 0x01 #define UCYCOM_DATA_BITS_5 0x00 /* Modem (Input) status byte */ #define UCYCOM_RI 0x80 #define UCYCOM_DCD 0x40 #define UCYCOM_DSR 0x20 #define UCYCOM_CTS 0x10 #define UCYCOM_ERROR 0x08 #define UCYCOM_LMASK 0x07 /* Modem (Output) control byte */ #define UCYCOM_DTR 0x20 #define UCYCOM_RTS 0x10 #define UCYCOM_ORESET 0x08 struct ucycom_softc { device_t sc_dev; struct uhidev *sc_hdev; struct usbd_device *sc_udev; struct usb_task sc_task; struct tty *sc_tty; enum { UCYCOM_INIT_NONE, UCYCOM_INIT_INITED } sc_init_state; kmutex_t sc_lock; /* protects refcnt, others */ /* uhidev parameters */ size_t sc_flen; /* feature report length */ size_t sc_olen; /* output report length */ uint8_t *sc_obuf; int sc_wlen; /* settings */ uint32_t sc_baud; uint8_t sc_cfg; /* Data format */ uint8_t sc_mcr; /* Modem control */ uint8_t sc_msr; /* Modem status */ int sc_swflags; /* flags */ char sc_dying; }; static dev_type_open(ucycomopen); static dev_type_close(ucycomclose); static dev_type_read(ucycomread); static dev_type_write(ucycomwrite); static dev_type_ioctl(ucycomioctl); static dev_type_stop(ucycomstop); static dev_type_tty(ucycomtty); static dev_type_poll(ucycompoll); const struct cdevsw ucycom_cdevsw = { .d_open = ucycomopen, .d_close = ucycomclose, .d_read = ucycomread, .d_write = ucycomwrite, .d_ioctl = ucycomioctl, .d_stop = ucycomstop, .d_tty = ucycomtty, .d_poll = ucycompoll, .d_mmap = nommap, .d_kqfilter = ttykqfilter, .d_discard = nodiscard, .d_flag = D_TTY }; Static int ucycomparam(struct tty *, struct termios *); Static void ucycomstart(struct tty *); Static void ucycomstarttask(void *); Static void ucycomwritecb(struct usbd_xfer *, void *, usbd_status); Static void ucycom_intr(void *, void *, u_int); Static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t); Static void tiocm_to_ucycom(struct ucycom_softc *, u_long, int); Static int ucycom_to_tiocm(struct ucycom_softc *); Static void ucycom_set_status(struct ucycom_softc *); Static void ucycom_dtr(struct ucycom_softc *, int); #if 0 Static void ucycom_rts(struct ucycom_softc *, int); #endif Static void ucycom_cleanup(struct ucycom_softc *); #ifdef UCYCOM_DEBUG Static void ucycom_get_cfg(struct ucycom_softc *); #endif Static const struct usb_devno ucycom_devs[] = { { USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_USBRS232 }, { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE }, }; #define ucycom_lookup(v, p) usb_lookup(ucycom_devs, v, p) static int ucycom_match(device_t, cfdata_t, void *); static void ucycom_attach(device_t, device_t, void *); static int ucycom_detach(device_t, int); static int ucycom_activate(device_t, enum devact); CFATTACH_DECL_NEW(ucycom, sizeof(struct ucycom_softc), ucycom_match, ucycom_attach, ucycom_detach, ucycom_activate); static int ucycom_match(device_t parent, cfdata_t match, void *aux) { struct uhidev_attach_arg *uha = aux; return ucycom_lookup(uha->uiaa->uiaa_vendor, uha->uiaa->uiaa_product) != NULL ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE; } static void ucycom_attach(device_t parent, device_t self, void *aux) { struct ucycom_softc *sc = device_private(self); struct uhidev_attach_arg *uha = aux; int size, repid; void *desc; sc->sc_dev = self; sc->sc_hdev = uha->parent; sc->sc_udev = uha->uiaa->uiaa_device; sc->sc_init_state = UCYCOM_INIT_NONE; uhidev_get_report_desc(uha->parent, &desc, &size); repid = uha->reportid; sc->sc_olen = hid_report_size(desc, size, hid_output, repid); sc->sc_flen = hid_report_size(desc, size, hid_feature, repid); if (sc->sc_olen != 8 && sc->sc_olen != 32) return; if (sc->sc_flen != 5) return; sc->sc_msr = sc->sc_mcr = 0; /* not MP-safe */ usb_init_task(&sc->sc_task, ucycomstarttask, sc, 0); /* set up tty */ sc->sc_tty = tty_alloc(); sc->sc_tty->t_sc = sc; sc->sc_tty->t_oproc = ucycomstart; sc->sc_tty->t_param = ucycomparam; tty_attach(sc->sc_tty); /* Nothing interesting to report */ aprint_normal("\n"); sc->sc_init_state = UCYCOM_INIT_INITED; } static int ucycom_detach(device_t self, int flags) { struct ucycom_softc *sc = device_private(self); struct tty *tp = sc->sc_tty; int maj, mn; int s; DPRINTF(("ucycom_detach: sc=%p flags=%d tp=%p\n", sc, flags, tp)); sc->sc_dying = 1; s = splusb(); if (tp != NULL) { ttylock(tp); CLR(tp->t_state, TS_CARR_ON); CLR(tp->t_cflag, CLOCAL | MDMBUF); ttyflush(tp, FREAD|FWRITE); ttyunlock(tp); } /* Wait for processes to go away. */ usb_detach_waitold(sc->sc_dev); splx(s); /* locate the major number */ maj = cdevsw_lookup_major(&ucycom_cdevsw); /* Nuke the vnodes for any open instances. */ mn = device_unit(self); DPRINTFN(2, ("ucycom_detach: maj=%d mn=%d\n", maj, mn)); vdevgone(maj, mn, mn, VCHR); vdevgone(maj, mn | UCYCOMDIALOUT_MASK, mn | UCYCOMDIALOUT_MASK, VCHR); vdevgone(maj, mn | UCYCOMCALLUNIT_MASK, mn | UCYCOMCALLUNIT_MASK, VCHR); usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER, NULL); /* Detach and free the tty. */ if (tp != NULL) { DPRINTF(("ucycom_detach: tty_detach %p\n", tp)); tty_detach(tp); tty_free(tp); sc->sc_tty = NULL; } return 0; } static int ucycom_activate(device_t self, enum devact act) { struct ucycom_softc *sc = device_private(self); DPRINTFN(5,("ucycom_activate: %d\n", act)); switch (act) { case DVACT_DEACTIVATE: sc->sc_dying = 1; return 0; default: return EOPNOTSUPP; } } #if 0 void ucycom_shutdown(struct ucycom_softc *sc) { struct tty *tp = sc->sc_tty; DPRINTF(("ucycom_shutdown\n")); /* * Hang up if necessary. Wait a bit, so the other side has time to * notice even if we immediately open the port again. */ if (ISSET(tp->t_cflag, HUPCL)) { ucycom_dtr(sc, 0); (void)tsleep(sc, TTIPRI, ttclos, hz); } } #endif static int ucycomopen(dev_t dev, int flag, int mode, struct lwp *l) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp; int s, err; DPRINTF(("ucycomopen: unit=%d\n", UCYCOMUNIT(dev))); DPRINTF(("ucycomopen: sc=%p\n", sc)); if (sc == NULL) return ENXIO; if (sc->sc_dying) return EIO; if (sc->sc_init_state != UCYCOM_INIT_INITED) return ENXIO; if (!device_is_active(sc->sc_dev)) return ENXIO; tp = sc->sc_tty; DPRINTF(("ucycomopen: tp=%p\n", tp)); if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp)) return EBUSY; s = spltty(); if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { struct termios t; tp->t_dev = dev; err = uhidev_open(sc->sc_hdev, &ucycom_intr, sc); if (err) { /* Any cleanup? */ splx(s); return err; } /* * Initialize the termios status to the defaults. Add in the * sticky bits from TIOCSFLAGS. */ t.c_ispeed = 0; t.c_ospeed = TTYDEF_SPEED; t.c_cflag = TTYDEF_CFLAG; if (ISSET(sc->sc_swflags, TIOCFLAG_CLOCAL)) SET(t.c_cflag, CLOCAL); if (ISSET(sc->sc_swflags, TIOCFLAG_CRTSCTS)) SET(t.c_cflag, CRTSCTS); if (ISSET(sc->sc_swflags, TIOCFLAG_MDMBUF)) SET(t.c_cflag, MDMBUF); tp->t_ospeed = 0; (void) ucycomparam(tp, &t); tp->t_iflag = TTYDEF_IFLAG; tp->t_oflag = TTYDEF_OFLAG; tp->t_lflag = TTYDEF_LFLAG; ttychars(tp); ttsetwater(tp); /* Allocate an output report buffer */ sc->sc_obuf = kmem_alloc(sc->sc_olen, KM_SLEEP); DPRINTF(("ucycomopen: sc->sc_obuf=%p\n", sc->sc_obuf)); #if 0 /* XXX Don't do this as for some reason trying to do an * XXX interrupt out transfer at this point means everything * XXX gets stuck!?! */ /* * Turn on DTR. We must always do this, even if carrier is not * present, because otherwise we'd have to use TIOCSDTR * immediately after setting CLOCAL, which applications do not * expect. We always assert DTR while the device is open * unless explicitly requested to deassert it. */ ucycom_dtr(sc, 1); #endif #if 0 /* XXX CLR(sc->sc_rx_flags, RX_ANY_BLOCK);*/ ucycom_hwiflow(sc); #endif } splx(s); err = ttyopen(tp, UCYCOMDIALOUT(dev), ISSET(flag, O_NONBLOCK)); if (err) goto bad; err = (*tp->t_linesw->l_open)(dev, tp); if (err) goto bad; return 0; bad: if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { /* * We failed to open the device, and nobody else had it opened. * Clean up the state as appropriate. */ ucycom_cleanup(sc); } return err; } static int ucycomclose(dev_t dev, int flag, int mode, struct lwp *l) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; DPRINTF(("ucycomclose: unit=%d\n", UCYCOMUNIT(dev))); if (!ISSET(tp->t_state, TS_ISOPEN)) return 0; (*tp->t_linesw->l_close)(tp, flag); ttyclose(tp); if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { /* * Although we got a last close, the device may still be in * use; e.g. if this was the dialout node, and there are still * processes waiting for carrier on the non-dialout node. */ ucycom_cleanup(sc); } return 0; } Static void ucycomstart(struct tty *tp) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(tp->t_dev)); usbd_status err __unused; u_char *data; int cnt, len, s; KASSERT(ttylocked(tp)); if (sc->sc_dying) return; s = spltty(); if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) { DPRINTFN(4,("ucycomstart: no go, state=%#x\n", tp->t_state)); goto out; } #if 0 /* HW FLOW CTL */ if (sc->sc_tx_stopped) goto out; #endif if (ttypull(tp) == 0) goto out; /* Grab the first contiguous region of buffer space. */ data = tp->t_outq.c_cf; cnt = ndqb(&tp->t_outq, 0); if (cnt == 0) { DPRINTF(("ucycomstart: cnt == 0\n")); goto out; } SET(tp->t_state, TS_BUSY); /* * The 8 byte output report uses byte 0 for control and byte * count. * * The 32 byte output report uses byte 0 for control. Byte 1 * is used for byte count. */ memset(sc->sc_obuf, 0, sc->sc_olen); len = cnt; switch (sc->sc_olen) { case 8: if (cnt > sc->sc_olen - 1) { DPRINTF(("ucycomstart(8): big buffer %d chars\n", len)); len = sc->sc_olen - 1; } memcpy(sc->sc_obuf + 1, data, len); sc->sc_obuf[0] = len | sc->sc_mcr; DPRINTF(("ucycomstart(8): sc->sc_obuf[0] = %d | %d = %d\n", len, sc->sc_mcr, sc->sc_obuf[0])); #ifdef UCYCOM_DEBUG if (ucycomdebug > 10) { uint32_t i; uint8_t *d = data; DPRINTF(("ucycomstart(8): data =")); for (i = 0; i < len; i++) DPRINTF((" %02x", d[i])); DPRINTF(("\n")); } #endif break; case 32: if (cnt > sc->sc_olen - 2) { DPRINTF(("ucycomstart(32): big buffer %d chars\n", len)); len = sc->sc_olen - 2; } memcpy(sc->sc_obuf + 2, data, len); sc->sc_obuf[0] = sc->sc_mcr; sc->sc_obuf[1] = len; DPRINTF(("ucycomstart(32): sc->sc_obuf[0] = %d\n" "sc->sc_obuf[1] = %d\n", sc->sc_obuf[0], sc->sc_obuf[1])); #ifdef UCYCOM_DEBUG if (ucycomdebug > 10) { uint32_t i; uint8_t *d = data; DPRINTF(("ucycomstart(32): data =")); for (i = 0; i < len; i++) DPRINTF((" %02x", d[i])); DPRINTF(("\n")); } #endif break; default: DPRINTFN(2,("ucycomstart: unknown output report size (%zd)\n", sc->sc_olen)); goto out; } splx(s); sc->sc_wlen = len; #ifdef UCYCOM_DEBUG if (ucycomdebug > 5) { int i; if (len != 0) { DPRINTF(("ucycomstart: sc->sc_obuf[0..%zd) =", sc->sc_olen)); for (i = 0; i < sc->sc_olen; i++) DPRINTF((" %02x", sc->sc_obuf[i])); DPRINTF(("\n")); } } #endif DPRINTFN(4,("ucycomstart: %d chars\n", len)); usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); return; out: splx(s); } Static void ucycomstarttask(void *cookie) { struct ucycom_softc *sc = cookie; usbd_status err; /* What can we do on error? */ err = uhidev_write_async(sc->sc_hdev, sc->sc_obuf, sc->sc_olen, 0, USBD_NO_TIMEOUT, ucycomwritecb, sc); #ifdef UCYCOM_DEBUG if (err != USBD_IN_PROGRESS) DPRINTF(("ucycomstart: err=%s\n", usbd_errstr(err))); #else __USE(err); #endif } Static void ucycomwritecb(struct usbd_xfer *xfer, void *p, usbd_status status) { struct ucycom_softc *sc = (struct ucycom_softc *)p; struct tty *tp = sc->sc_tty; usbd_status stat; int len, s; if (status == USBD_CANCELLED || sc->sc_dying) goto error; if (status) { DPRINTF(("ucycomwritecb: status=%d\n", status)); /* XXX we should restart after some delay. */ goto error; } usbd_get_xfer_status(xfer, NULL, NULL, &len, &stat); if (status != USBD_NORMAL_COMPLETION) { DPRINTFN(4,("ucycomwritecb: status = %d\n", status)); goto error; } DPRINTFN(4,("ucycomwritecb: did %d/%d chars\n", sc->sc_wlen, len)); s = spltty(); CLR(tp->t_state, TS_BUSY); if (ISSET(tp->t_state, TS_FLUSH)) CLR(tp->t_state, TS_FLUSH); else ndflush(&tp->t_outq, sc->sc_wlen); (*tp->t_linesw->l_start)(tp); splx(s); return; error: s = spltty(); CLR(tp->t_state, TS_BUSY); splx(s); } Static int ucycomparam(struct tty *tp, struct termios *t) { struct ucycom_softc *sc = tp->t_sc; uint32_t baud; uint8_t cfg; int err; if (t->c_ospeed < 0) { DPRINTF(("ucycomparam: c_ospeed < 0\n")); return EINVAL; } /* Check requested parameters. */ if (t->c_ispeed && t->c_ispeed != t->c_ospeed) return EINVAL; /* * For the console, always force CLOCAL and !HUPCL, so that the port * is always active. */ if (ISSET(sc->sc_swflags, TIOCFLAG_SOFTCAR)) { SET(t->c_cflag, CLOCAL); CLR(t->c_cflag, HUPCL); } /* * If there were no changes, don't do anything. This avoids dropping * input and improves performance when all we did was frob things like * VMIN and VTIME. */ if (tp->t_ospeed == t->c_ospeed && tp->t_cflag == t->c_cflag) return 0; /* XXX lcr = ISSET(sc->sc_lcr, LCR_SBREAK) | cflag2lcr(t->c_cflag); */ /* And copy to tty. */ tp->t_ispeed = 0; tp->t_ospeed = t->c_ospeed; tp->t_cflag = t->c_cflag; baud = t->c_ispeed; DPRINTF(("ucycomparam: baud=%d\n", baud)); if (t->c_cflag & CIGNORE) { cfg = sc->sc_cfg; } else { cfg = 0; switch (t->c_cflag & CSIZE) { case CS8: cfg |= UCYCOM_DATA_BITS_8; break; case CS7: cfg |= UCYCOM_DATA_BITS_7; break; case CS6: cfg |= UCYCOM_DATA_BITS_6; break; case CS5: cfg |= UCYCOM_DATA_BITS_5; break; default: return EINVAL; } cfg |= ISSET(t->c_cflag, CSTOPB) ? UCYCOM_STOP_BITS_2 : UCYCOM_STOP_BITS_1; cfg |= ISSET(t->c_cflag, PARENB) ? UCYCOM_PARITY_ON : UCYCOM_PARITY_OFF; cfg |= ISSET(t->c_cflag, PARODD) ? UCYCOM_PARITY_ODD : UCYCOM_PARITY_EVEN; } /* * Update the tty layer's idea of the carrier bit, in case we changed * CLOCAL or MDMBUF. We don't hang up here; we only do that by * explicit request. */ DPRINTF(("ucycomparam: l_modem\n")); (void) (*tp->t_linesw->l_modem)(tp, 1 /* XXX carrier */ ); err = ucycom_configure(sc, baud, cfg); return err; } static void ucycomstop(struct tty *tp, int flag) { DPRINTF(("ucycomstop: flag=%d\n", flag)); } static int ucycomread(dev_t dev, struct uio *uio, int flag) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; int err; DPRINTF(("ucycomread: sc=%p, tp=%p, uio=%p, flag=%d\n", sc, tp, uio, flag)); if (sc->sc_dying) return EIO; err = ((*tp->t_linesw->l_read)(tp, uio, flag)); return err; } static int ucycomwrite(dev_t dev, struct uio *uio, int flag) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; int err; DPRINTF(("ucycomwrite: sc=%p, tp=%p, uio=%p, flag=%d\n", sc, tp, uio, flag)); if (sc->sc_dying) return EIO; err = ((*tp->t_linesw->l_write)(tp, uio, flag)); return err; } static struct tty * ucycomtty(dev_t dev) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; DPRINTF(("ucycomtty: sc=%p, tp=%p\n", sc, tp)); return tp; } static int ucycomioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; int err; int s; if (sc->sc_dying) return EIO; DPRINTF(("ucycomioctl: sc=%p, tp=%p, data=%p\n", sc, tp, data)); err = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l); if (err != EPASSTHROUGH) return err; err = ttioctl(tp, cmd, data, flag, l); if (err != EPASSTHROUGH) return err; err = 0; DPRINTF(("ucycomioctl: our cmd=0x%08lx\n", cmd)); s = spltty(); switch (cmd) { /* case TIOCSBRK: ucycom_break(sc, 1); break; case TIOCCBRK: ucycom_break(sc, 0); break; */ case TIOCSDTR: ucycom_dtr(sc, 1); break; case TIOCCDTR: ucycom_dtr(sc, 0); break; case TIOCGFLAGS: *(int *)data = sc->sc_swflags; break; case TIOCSFLAGS: err = kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_PRIVSET, tp); if (err) break; sc->sc_swflags = *(int *)data; break; case TIOCMSET: case TIOCMBIS: case TIOCMBIC: tiocm_to_ucycom(sc, cmd, *(int *)data); break; case TIOCMGET: *(int *)data = ucycom_to_tiocm(sc); break; default: err = EPASSTHROUGH; break; } splx(s); return err; } static int ucycompoll(dev_t dev, int events, struct lwp *l) { struct ucycom_softc *sc = device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev)); struct tty *tp = sc->sc_tty; int err; DPRINTF(("ucycompoll: sc=%p, tp=%p, events=%d, lwp=%p\n", sc, tp, events, l)); if (sc->sc_dying) return EIO; err = ((*tp->t_linesw->l_poll)(tp, events, l)); return err; } Static int ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) { uint8_t report[5]; int err; switch (baud) { case 600: case 1200: case 2400: case 4800: case 9600: case 19200: case 38400: case 57600: #if 0 /* * Stock chips only support standard baud rates in the 600 - 57600 * range, but higher rates can be achieved using custom firmware. */ case 115200: case 153600: case 192000: #endif break; default: return EINVAL; } DPRINTF(("ucycom_configure: setting %d baud, %d-%c-%d (%d)\n", baud, 5 + (cfg & UCYCOM_DATA_MASK), (cfg & UCYCOM_PARITY_MASK) ? ((cfg & UCYCOM_PARITY_TYPE_MASK) ? 'O' : 'E') : 'N', (cfg & UCYCOM_STOP_MASK) ? 2 : 1, cfg)); report[0] = baud & 0xff; report[1] = (baud >> 8) & 0xff; report[2] = (baud >> 16) & 0xff; report[3] = (baud >> 24) & 0xff; report[4] = cfg; err = uhidev_set_report(sc->sc_hdev, UHID_FEATURE_REPORT, report, sc->sc_flen); if (err != 0) { DPRINTF(("%s\n", usbd_errstr(err))); return EIO; } sc->sc_baud = baud; sc->sc_cfg = cfg; #ifdef UCYCOM_DEBUG ucycom_get_cfg(sc); #endif return 0; } Static void ucycom_intr(void *cookie, void *ibuf, u_int len) { struct ucycom_softc *sc = cookie; struct tty *tp = sc->sc_tty; int (*rint)(int , struct tty *) = tp->t_linesw->l_rint; uint8_t *cp = ibuf; int s, n, st, chg; /* We understand 8 byte and 32 byte input records */ switch (len) { case 8: n = cp[0] & UCYCOM_LMASK; st = cp[0] & ~UCYCOM_LMASK; cp++; break; case 32: st = cp[0]; n = cp[1]; cp += 2; n = uimin(n, 30); break; default: DPRINTFN(3,("ucycom_intr: Unknown input report length\n")); return; } #ifdef UCYCOM_DEBUG if (ucycomdebug > 5) { uint32_t i; if (n != 0) { DPRINTF(("ucycom_intr: ibuf[0..%d) =", n)); for (i = 0; i < n; i++) DPRINTF((" %02x", cp[i])); DPRINTF(("\n")); } } #endif /* Give characters to tty layer. */ s = spltty(); while (n-- > 0) { DPRINTFN(7,("ucycom_intr: char=0x%02x\n", *cp)); if ((*rint)(*cp++, tp) == -1) { /* XXX what should we do? */ aprint_error_dev(sc->sc_dev, "lost a character\n"); break; } } splx(s); chg = st ^ sc->sc_msr; sc->sc_msr = st; if (ISSET(chg, UCYCOM_DCD)) (*tp->t_linesw->l_modem)(tp, ISSET(sc->sc_msr, UCYCOM_DCD)); } Static void tiocm_to_ucycom(struct ucycom_softc *sc, u_long how, int ttybits) { u_char combits; u_char before = sc->sc_mcr; combits = 0; if (ISSET(ttybits, TIOCM_DTR)) SET(combits, UCYCOM_DTR); if (ISSET(ttybits, TIOCM_RTS)) SET(combits, UCYCOM_RTS); switch (how) { case TIOCMBIC: CLR(sc->sc_mcr, combits); break; case TIOCMBIS: SET(sc->sc_mcr, combits); break; case TIOCMSET: CLR(sc->sc_mcr, UCYCOM_DTR | UCYCOM_RTS); SET(sc->sc_mcr, combits); break; } if (before ^ sc->sc_mcr) { DPRINTF(("tiocm_to_ucycom: something has changed\n")); ucycom_set_status(sc); } } Static int ucycom_to_tiocm(struct ucycom_softc *sc) { u_char combits; int ttybits = 0; combits = sc->sc_mcr; if (ISSET(combits, UCYCOM_DTR)) SET(ttybits, TIOCM_DTR); if (ISSET(combits, UCYCOM_RTS)) SET(ttybits, TIOCM_RTS); combits = sc->sc_msr; if (ISSET(combits, UCYCOM_DCD)) SET(ttybits, TIOCM_CD); if (ISSET(combits, UCYCOM_CTS)) SET(ttybits, TIOCM_CTS); if (ISSET(combits, UCYCOM_DSR)) SET(ttybits, TIOCM_DSR); if (ISSET(combits, UCYCOM_RI)) SET(ttybits, TIOCM_RI); return ttybits; } Static void ucycom_dtr(struct ucycom_softc *sc, int set) { uint8_t old; old = sc->sc_mcr; if (set) SET(sc->sc_mcr, UCYCOM_DTR); else CLR(sc->sc_mcr, UCYCOM_DTR); if (old ^ sc->sc_mcr) ucycom_set_status(sc); } #if 0 Static void ucycom_rts(struct ucycom_softc *sc, int set) { uint8_t old; old = sc->sc_msr; if (set) SET(sc->sc_mcr, UCYCOM_RTS); else CLR(sc->sc_mcr, UCYCOM_RTS); if (old ^ sc->sc_mcr) ucycom_set_status(sc); } #endif Static void ucycom_set_status(struct ucycom_softc *sc) { int err; if (sc->sc_olen != 8 && sc->sc_olen != 32) { DPRINTFN(2,("ucycom_set_status: unknown output report " "size (%zd)\n", sc->sc_olen)); return; } DPRINTF(("ucycom_set_status: %d\n", sc->sc_mcr)); memset(sc->sc_obuf, 0, sc->sc_olen); sc->sc_obuf[0] = sc->sc_mcr; err = uhidev_write(sc->sc_hdev, sc->sc_obuf, sc->sc_olen); if (err) { DPRINTF(("ucycom_set_status: err=%d\n", err)); } } #ifdef UCYCOM_DEBUG Static void ucycom_get_cfg(struct ucycom_softc *sc) { int err, cfg, baud; uint8_t report[5]; err = uhidev_get_report(sc->sc_hdev, UHID_FEATURE_REPORT, report, sc->sc_flen); if (err) { DPRINTF(("%s: failed\n", __func__)); return; } cfg = report[4]; baud = (report[3] << 24) + (report[2] << 16) + (report[1] << 8) + report[0]; DPRINTF(("ucycom_get_cfg: device reports %d baud, %d-%c-%d (%d)\n", baud, 5 + (cfg & UCYCOM_DATA_MASK), (cfg & UCYCOM_PARITY_MASK) ? ((cfg & UCYCOM_PARITY_TYPE_MASK) ? 'O' : 'E') : 'N', (cfg & UCYCOM_STOP_MASK) ? 2 : 1, cfg)); } #endif Static void ucycom_cleanup(struct ucycom_softc *sc) { uint8_t *obuf; DPRINTF(("ucycom_cleanup: closing uhidev\n")); obuf = sc->sc_obuf; sc->sc_obuf = NULL; uhidev_close(sc->sc_hdev); if (obuf != NULL) kmem_free(obuf, sc->sc_olen); }