#include <sys/param.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdevs.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/ucomvar.h>
#ifdef UKSPAN_DEBUG
#define DPRINTF(x...) do { printf(x); } while (0)
#else
#define DPRINTF(x...) do { ; } while (0)
#endif
#define UKSPAN_PARITY_NONE 0x0
#define UKSPAN_PARITY_ODD 0x08
#define UKSPAN_PARITY_EVEN 0x18
#define UKSPAN_DATA_5 0x0
#define UKSPAN_DATA_6 0x1
#define UKSPAN_DATA_7 0x2
#define UKSPAN_DATA_8 0x3
#define UKSPAN_STOP_1 0x0
#define UKSPAN_STOP_2 0x4
#define UKSPAN_MAGIC 0x2
#define UKSPAN_CLOCK 14769231
#define UKSPAN_CONFIG_IDX 1
#define UKSPAN_IFACE_IDX 0
#define UKSPAN_EA_BULKIN (UE_DIR_IN | 1)
#define UKSPAN_EA_BULKOUT (UE_DIR_OUT | 1)
#define UKSPAN_EA_CONFIGIN (UE_DIR_IN | 2)
#define UKSPAN_EA_CONFIGOUT (UE_DIR_OUT | 2)
struct ukspan_cmsg {
uint8_t setclock;
uint8_t baudlo;
uint8_t baudhi;
uint8_t setlcr;
uint8_t lcr;
uint8_t setrxmode;
uint8_t rxmode;
uint8_t settxmode;
uint8_t txmode;
uint8_t settxflowcontrol;
uint8_t txflowcontrol;
uint8_t setrxflowcontrol;
uint8_t rxflowcontrol;
uint8_t sendxoff;
uint8_t sendxon;
uint8_t xonchar;
uint8_t xoffchar;
uint8_t sendchar;
uint8_t txchar;
uint8_t setrts;
uint8_t rts;
uint8_t setdtr;
uint8_t dtr;
uint8_t rxforwardingchars;
uint8_t rxforwardingtimeoutms;
uint8_t txacksetting;
uint8_t portenabled;
uint8_t txflush;
uint8_t txbreak;
uint8_t loopbackmode;
uint8_t rxflush;
uint8_t rxforward;
uint8_t cancelrxoff;
uint8_t returnstatus;
} __packed;
struct ukspan_smsg {
uint8_t msr;
uint8_t cts;
uint8_t dcd;
uint8_t dsr;
uint8_t ri;
uint8_t txxoff;
uint8_t rxbreak;
uint8_t rxoverrun;
uint8_t rxparity;
uint8_t rxframe;
uint8_t portstate;
uint8_t messageack;
uint8_t charack;
uint8_t controlresp;
} __packed;
struct ukspan_softc {
struct device sc_dev;
struct usbd_device *udev;
struct usbd_interface *iface;
struct usbd_pipe *cout_pipe;
struct usbd_pipe *cin_pipe;
struct usbd_xfer *ixfer;
struct usbd_xfer *oxfer;
struct device *ucom_dev;
struct ukspan_smsg smsg;
struct ukspan_cmsg cmsg;
u_char lsr;
u_char msr;
};
int ukspan_match(struct device *, void *, void *);
void ukspan_attach(struct device *, struct device *, void *);
int ukspan_detach(struct device *, int);
void ukspan_close(void *, int);
int ukspan_open(void *, int);
int ukspan_param(void *, int, struct termios *);
void ukspan_set(void *, int, int, int);
void ukspan_get_status(void *, int, u_char *, u_char *);
void ukspan_cmsg_init(bool, struct ukspan_cmsg *);
int ukspan_cmsg_send(struct ukspan_softc *);
void ukspan_incb(struct usbd_xfer *, void *, usbd_status);
void ukspan_outcb(struct usbd_xfer *, void *, usbd_status);
void ukspan_destroy(struct ukspan_softc *);
struct cfdriver ukspan_cd = {
NULL, "ukspan", DV_DULL
};
const struct cfattach ukspan_ca = {
sizeof(struct ukspan_softc), ukspan_match, ukspan_attach,
ukspan_detach
};
static const struct usb_devno ukspan_devs[] = {
{ USB_VENDOR_KEYSPAN, USB_PRODUCT_KEYSPAN_USA19HS },
};
static const struct ucom_methods ukspan_methods = {
.ucom_get_status = ukspan_get_status,
.ucom_set = ukspan_set,
.ucom_param = ukspan_param,
.ucom_ioctl = NULL,
.ucom_open = ukspan_open,
.ucom_close = ukspan_close,
.ucom_read = NULL,
.ucom_write = NULL,
};
int
ukspan_match(struct device *parent, void *match, void *aux)
{
struct usb_attach_arg *uaa = aux;
if (uaa->iface != NULL)
return UMATCH_NONE;
int found = usb_lookup(ukspan_devs, uaa->vendor, uaa->product) != NULL;
return found ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
}
void
ukspan_attach(struct device *parent, struct device *self, void *aux)
{
struct ukspan_softc *sc = (struct ukspan_softc *)self;
struct usb_attach_arg *uaa = aux;
struct usbd_device *dev = uaa->device;
struct ucom_attach_args uca = {0};
usb_endpoint_descriptor_t *ed;
const char *devname = sc->sc_dev.dv_xname;
usbd_status err;
int t1, t2, t3, t4;
DPRINTF("attach\n");
sc->udev = dev;
sc->cin_pipe = sc->cout_pipe = NULL;
sc->ixfer = sc->oxfer = NULL;
sc->ucom_dev = NULL;
err = usbd_set_config_index(sc->udev, UKSPAN_CONFIG_IDX, 1);
if (err) {
printf("%s: set config failed\n", devname);
goto fail;
}
err = usbd_device2interface_handle(sc->udev, UKSPAN_IFACE_IDX,
&sc->iface);
if (err) {
printf("%s: get interface failed\n", devname);
goto fail;
}
ed = usbd_get_endpoint_descriptor(sc->iface, UKSPAN_EA_BULKIN);
t1 = UE_GET_XFERTYPE(ed->bmAttributes);
uca.ibufsize = UGETW(ed->wMaxPacketSize);
uca.bulkin = UKSPAN_EA_BULKIN;
ed = usbd_get_endpoint_descriptor(sc->iface, UKSPAN_EA_BULKOUT);
t2 = UE_GET_XFERTYPE(ed->bmAttributes);
uca.obufsize = UGETW(ed->wMaxPacketSize);
uca.bulkout = UKSPAN_EA_BULKOUT;
ed = usbd_get_endpoint_descriptor(sc->iface, UKSPAN_EA_CONFIGIN);
t3 = UE_GET_XFERTYPE(ed->bmAttributes);
if (UGETW(ed->wMaxPacketSize) < sizeof(struct ukspan_smsg)) {
printf("%s: in config packet size too small\n", devname);
goto fail;
}
ed = usbd_get_endpoint_descriptor(sc->iface, UKSPAN_EA_CONFIGOUT);
t4 = UE_GET_XFERTYPE(ed->bmAttributes);
if (UGETW(ed->wMaxPacketSize) < sizeof(struct ukspan_cmsg)) {
printf("%s: out config packet size too small\n", devname);
goto fail;
}
if (t1 != UE_BULK || t2 != UE_BULK || t3 != UE_BULK || t4 != UE_BULK) {
printf("%s: unexpected xfertypes %x %x %x %x != %x\n", devname,
t1, t2, t3, t4, UE_BULK);
goto fail;
}
err = usbd_open_pipe(sc->iface, UKSPAN_EA_CONFIGOUT, USBD_EXCLUSIVE_USE,
&sc->cout_pipe);
if (err) {
printf("%s: failed to create control out pipe\n", devname);
goto fail;
}
err = usbd_open_pipe(sc->iface, UKSPAN_EA_CONFIGIN, USBD_EXCLUSIVE_USE,
&sc->cin_pipe);
if (err) {
printf("%s: failed to create control out pipe\n", devname);
goto fail;
}
sc->ixfer = usbd_alloc_xfer(sc->udev);
sc->oxfer = usbd_alloc_xfer(sc->udev);
if (!sc->ixfer || !sc->oxfer) {
printf("%s: failed to allocate xfers\n", devname);
goto fail;
}
usbd_setup_xfer(sc->ixfer, sc->cin_pipe, sc, &sc->smsg,
sizeof(sc->smsg), 0, USBD_NO_TIMEOUT, ukspan_incb);
err = usbd_transfer(sc->ixfer);
if (err && err != USBD_IN_PROGRESS) {
printf("%s: failed to start ixfer\n", devname);
goto fail;
}
uca.portno = UCOM_UNK_PORTNO;
uca.ibufsizepad = uca.ibufsize;
uca.opkthdrlen = 0;
uca.device = dev;
uca.iface = sc->iface;
uca.methods = &ukspan_methods;
uca.arg = sc;
uca.info = NULL;
sc->ucom_dev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
DPRINTF("attach done\n");
return;
fail:
ukspan_destroy(sc);
usbd_deactivate(sc->udev);
}
int
ukspan_detach(struct device *self, int flags)
{
struct ukspan_softc *sc = (struct ukspan_softc *)self;
DPRINTF("detach\n");
ukspan_destroy(sc);
if (sc->ucom_dev) {
config_detach(sc->ucom_dev, flags);
sc->ucom_dev = NULL;
}
return 0;
}
void
ukspan_outcb(struct usbd_xfer *xfer, void *priv, usbd_status status)
{
struct ukspan_softc *sc = priv;
const char *devname = sc->sc_dev.dv_xname;
DPRINTF("outcb\n");
if (usbd_is_dying(sc->udev)) {
DPRINTF("usb dying\n");
return;
}
if (status != USBD_NORMAL_COMPLETION) {
printf("%s: oxfer failed\n", devname);
return;
}
}
void
ukspan_incb(struct usbd_xfer *xfer, void *priv, usbd_status status)
{
struct ukspan_softc *sc = priv;
const char *devname = sc->sc_dev.dv_xname;
const struct ukspan_smsg *smsg = &sc->smsg;
usbd_status err;
u_int32_t len;
DPRINTF("incb\n");
if (usbd_is_dying(sc->udev)) {
printf("%s: usb dying\n", devname);
return;
}
if (!sc->cin_pipe || !sc->ixfer) {
printf("%s: no cin_pipe, but not dying?\n", devname);
return;
}
if (status != USBD_NORMAL_COMPLETION) {
if (status != USBD_NOT_STARTED && status != USBD_CANCELLED)
printf("%s: ixfer failed\n", devname);
return;
}
usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL);
if (len < sizeof(struct ukspan_smsg)) {
printf("%s: short read\n", devname);
return;
}
sc->msr = smsg->msr;
sc->lsr = (smsg->rxoverrun ? ULSR_OE : 0) |
(smsg->rxparity ? ULSR_PE : 0) |
(smsg->rxframe ? ULSR_FE : 0) |
(smsg->rxbreak ? ULSR_BI : 0);
ucom_status_change((struct ucom_softc *)sc->ucom_dev);
usbd_setup_xfer(sc->ixfer, sc->cin_pipe, sc, &sc->smsg,
sizeof(sc->smsg), USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT,
ukspan_incb);
err = usbd_transfer(sc->ixfer);
if (err && err != USBD_IN_PROGRESS)
printf("%s: usbd transfer failed\n", devname);
}
void
ukspan_get_status(void *addr, int portno, u_char *lsr, u_char *msr)
{
struct ukspan_softc *sc = addr;
DPRINTF("get status\n");
if (lsr)
*lsr = sc->lsr;
if (msr)
*msr = sc->msr;
}
void
ukspan_cmsg_init(bool opening, struct ukspan_cmsg *omsg)
{
bzero(omsg, sizeof(*omsg));
omsg->xonchar = 17;
omsg->xoffchar = 19;
omsg->rxforwardingchars = 16;
omsg->rxforwardingtimeoutms = 16;
omsg->txacksetting = 0;
omsg->txbreak = 0;
if (opening) {
omsg->portenabled = 1;
omsg->rxflush = 1;
}
}
int
ukspan_cmsg_send(struct ukspan_softc *sc)
{
const char *devname = sc->sc_dev.dv_xname;
usbd_status err;
usbd_setup_xfer(sc->oxfer, sc->cout_pipe, sc, &sc->cmsg,
sizeof(sc->cmsg), USBD_SYNCHRONOUS, USBD_NO_TIMEOUT, ukspan_outcb);
err = usbd_transfer(sc->oxfer);
if (err != USBD_NORMAL_COMPLETION) {
printf("%s: control xfer failed\n", devname);
return EIO;
}
return 0;
}
void
ukspan_set(void *addr, int portno, int reg, int onoff)
{
struct ukspan_softc *sc = addr;
const char *devname = sc->sc_dev.dv_xname;
DPRINTF("set %#x = %#x\n", reg, onoff);
int flag = !!onoff;
switch (reg) {
case UCOM_SET_DTR:
sc->cmsg.setdtr = 1;
sc->cmsg.dtr = flag;
break;
case UCOM_SET_RTS:
sc->cmsg.setrts = 1;
sc->cmsg.rts = flag;
break;
case UCOM_SET_BREAK:
sc->cmsg.txbreak = flag;
break;
default:
printf("%s: unhandled reg %#x\n", devname, reg);
return;
}
ukspan_cmsg_send(sc);
}
int
ukspan_param(void *addr, int portno, struct termios *ti)
{
struct ukspan_softc *sc = addr;
const char *devname = sc->sc_dev.dv_xname;
struct ukspan_cmsg *cmsg = &sc->cmsg;
speed_t baud;
tcflag_t cflag;
u_int32_t div;
u_int8_t lcr;
DPRINTF("param: %#x %#x %#x\n", ti->c_ospeed, ti->c_cflag, ti->c_iflag);
div = 1;
baud = ti->c_ospeed;
switch (baud) {
case B300:
case B600:
case B1200:
case B2400:
case B4800:
case B9600:
case B19200:
case B38400:
case B57600:
case B115200:
case B230400:
div = UKSPAN_CLOCK / (baud * 16);
break;
default:
printf("%s: unexpected baud: %d\n", devname, baud);
return EINVAL;
}
cmsg->setclock = 1;
cmsg->baudlo = div & 0xff;
cmsg->baudhi = div >> 8;
cmsg->setrxmode = 1;
cmsg->settxmode = 1;
if (baud > 57600)
cmsg->rxmode = cmsg->txmode = UKSPAN_MAGIC;
else
cmsg->rxmode = cmsg->txmode = 0;
cflag = ti->c_cflag;
if ((cflag & CIGNORE) == 0) {
if (cflag & PARENB)
lcr = (cflag & PARODD) ? UKSPAN_PARITY_ODD :
UKSPAN_PARITY_EVEN;
else
lcr = UKSPAN_PARITY_NONE;
switch (cflag & CSIZE) {
case CS5:
lcr |= UKSPAN_DATA_5;
break;
case CS6:
lcr |= UKSPAN_DATA_6;
break;
case CS7:
lcr |= UKSPAN_DATA_7;
break;
case CS8:
lcr |= UKSPAN_DATA_8;
break;
}
lcr |= (cflag & CSTOPB) ? UKSPAN_STOP_2 : UKSPAN_STOP_1;
cmsg->setlcr = 1;
cmsg->lcr = lcr;
}
ukspan_cmsg_send(sc);
return 0;
}
int
ukspan_open(void *addr, int portno)
{
struct ukspan_softc *sc = addr;
int ret;
DPRINTF("open\n");
if (usbd_is_dying(sc->udev)) {
DPRINTF("usb dying\n");
return ENXIO;
}
ukspan_cmsg_init(true, &sc->cmsg);
ret = ukspan_cmsg_send(sc);
return ret;
}
void
ukspan_close(void *addr, int portno)
{
struct ukspan_softc *sc = addr;
DPRINTF("close\n");
if (usbd_is_dying(sc->udev)) {
DPRINTF("usb dying\n");
return;
}
ukspan_cmsg_init(false, &sc->cmsg);
ukspan_cmsg_send(sc);
}
void
ukspan_destroy(struct ukspan_softc *sc)
{
DPRINTF("destroy\n");
if (sc->cin_pipe) {
usbd_close_pipe(sc->cin_pipe);
sc->cin_pipe = NULL;
}
if (sc->cout_pipe) {
usbd_close_pipe(sc->cout_pipe);
sc->cout_pipe = NULL;
}
if (sc->oxfer) {
usbd_free_xfer(sc->oxfer);
sc->oxfer = NULL;
}
if (sc->ixfer) {
usbd_free_xfer(sc->ixfer);
sc->ixfer = NULL;
}
}