#include <sys/stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/caps.h>
#include <sys/cons.h>
#include <sys/serial.h>
#include <sys/thread2.h>
#include <sys/conf.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#define USB_DEBUG_VAR ucom_debug
#include <bus/u4b/usb_debug.h>
#include <bus/u4b/usb_busdma.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/serial/usb_serial.h>
static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
static int ucom_pps_mode;
SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RW,
&ucom_pps_mode, 0, "pulse capturing mode - 0/1/2 - disabled/CTS/DCD");
TUNABLE_INT("hw.usb.ucom.pss_mode", &ucom_pps_mode);
#ifdef USB_DEBUG
static int ucom_debug = 0;
SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW,
&ucom_debug, 0, "ucom debug level");
#endif
#define UCOM_CONS_BUFSIZE 1024
static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE];
static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE];
static unsigned int ucom_cons_rx_low = 0;
static unsigned int ucom_cons_rx_high = 0;
static unsigned int ucom_cons_tx_low = 0;
static unsigned int ucom_cons_tx_high = 0;
static int ucom_cons_unit = -1;
static int ucom_cons_subunit = 0;
static int ucom_cons_baud = 9600;
static struct ucom_softc *ucom_cons_softc = NULL;
TUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit);
SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW,
&ucom_cons_unit, 0, "console unit number");
TUNABLE_INT("hw.usb.ucom.cons_subunit", &ucom_cons_subunit);
SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RW,
&ucom_cons_subunit, 0, "console subunit number");
TUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud);
SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW,
&ucom_cons_baud, 0, "console baud rate");
static usb_proc_callback_t ucom_cfg_start_transfers;
static usb_proc_callback_t ucom_cfg_open;
static usb_proc_callback_t ucom_cfg_close;
static usb_proc_callback_t ucom_cfg_line_state;
static usb_proc_callback_t ucom_cfg_status_change;
static usb_proc_callback_t ucom_cfg_param;
static int ucom_unit_alloc(void);
static void ucom_unit_free(int);
static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
static void ucom_queue_command(struct ucom_softc *,
usb_proc_callback_t *, struct termios *pt,
struct usb_proc_msg *t0, struct usb_proc_msg *t1);
static void ucom_shutdown(struct ucom_softc *);
static void ucom_ring(struct ucom_softc *, uint8_t);
static void ucom_break(struct ucom_softc *, uint8_t);
static void ucom_dtr(struct ucom_softc *, uint8_t);
static void ucom_rts(struct ucom_softc *, uint8_t);
static int ucom_open(struct ucom_softc *sc);
static int ucom_close(struct ucom_softc *sc);
static void ucom_start(struct tty *tp);
static void ucom_stop(struct tty *tp, int);
static int ucom_param(struct tty *tp, struct termios *t);
static int ucom_modem(struct tty *tp, int sigon, int sigoff);
static int ucom_fromtio(int);
static int ucom_totio(int);
static void disc_optim(struct tty *, struct termios *, struct ucom_softc *);
static d_open_t ucom_dev_open;
static d_close_t ucom_dev_close;
static d_read_t ucom_dev_read;
static d_write_t ucom_dev_write;
static d_ioctl_t ucom_dev_ioctl;
static struct dev_ops ucom_ops = {
{ "ucom", 0, D_MPSAFE | D_TTY },
.d_open = ucom_dev_open,
.d_close = ucom_dev_close,
.d_read = ucom_dev_read,
.d_write = ucom_dev_write,
.d_ioctl = ucom_dev_ioctl,
.d_kqfilter = ttykqfilter,
.d_revoke = ttyrevoke
};
static moduledata_t ucom_mod = {
"ucom",
NULL,
NULL
};
DECLARE_MODULE(ucom, ucom_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(ucom, usb, 1, 1, 1);
MODULE_VERSION(ucom, UCOM_MODVER);
#define tty_gone(tp) ((tp->t_state) & (TS_ZOMBIE))
#define UCOM_UNIT_MAX 128
#define UCOM_TTY_PREFIX "ucom"
#define CALLOUT_MASK 0x80
#define CONTROL_MASK 0x60
#define CONTROL_INIT_STATE 0x20
#define CONTROL_LOCK_STATE 0x40
static struct unrhdr *ucom_unrhdr;
static struct lock ucom_lock;
static int ucom_close_refs;
static void
ucom_init(void *arg)
{
DPRINTF("\n");
ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL);
lockinit(&ucom_lock, "UCOM LOCK", 0, 0);
}
SYSINIT(ucom_init, SI_BOOT2_KLD - 1, SI_ORDER_ANY, ucom_init, NULL);
static void
ucom_uninit(void *arg)
{
struct unrhdr *hdr;
hdr = ucom_unrhdr;
ucom_unrhdr = NULL;
DPRINTF("\n");
if (hdr != NULL)
delete_unrhdr(hdr);
lockuninit(&ucom_lock);
}
SYSUNINIT(ucom_uninit, SI_BOOT2_KLD - 2, SI_ORDER_ANY, ucom_uninit, NULL);
static int
ucom_unit_alloc(void)
{
int unit;
if (ucom_unrhdr == NULL) {
DPRINTF("ucom_unrhdr is NULL\n");
return (-1);
}
unit = alloc_unr(ucom_unrhdr);
DPRINTF("unit %d is allocated\n", unit);
return (unit);
}
static void
ucom_unit_free(int unit)
{
if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) {
DPRINTF("cannot free unit number\n");
return;
}
DPRINTF("unit %d is freed\n", unit);
free_unr(ucom_unrhdr, unit);
}
int
ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
int subunits, void *parent,
const struct ucom_callback *callback, struct lock *lock)
{
int subunit;
int error = 0;
if ((sc == NULL) ||
(subunits <= 0) ||
(callback == NULL) ||
(lock == NULL)) {
return (EINVAL);
}
sc->sc_lock = lock;
ssc->sc_unit = ucom_unit_alloc();
if (ssc->sc_unit == -1)
return (ENOMEM);
ksnprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname),
UCOM_TTY_PREFIX "%d", ssc->sc_unit);
error = usb_proc_create(&ssc->sc_tq, lock, "ucom", USB_PRI_MED);
if (error) {
ucom_unit_free(ssc->sc_unit);
return (error);
}
ssc->sc_subunits = subunits;
ssc->sc_flag = UCOM_FLAG_ATTACHED |
UCOM_FLAG_FREE_UNIT;
if (callback->ucom_free == NULL)
ssc->sc_flag |= UCOM_FLAG_WAIT_REFS;
ucom_ref(ssc);
for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
sc[subunit].sc_subunit = subunit;
sc[subunit].sc_super = ssc;
sc[subunit].sc_lock = lock;
sc[subunit].sc_parent = parent;
sc[subunit].sc_callback = callback;
error = ucom_attach_tty(ssc, &sc[subunit]);
if (error) {
ucom_detach(ssc, &sc[0]);
return (error);
}
ucom_ref(ssc);
sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED;
}
DPRINTF("tp = %p, unit = %d, subunits = %d\n",
sc->sc_tty, ssc->sc_unit, ssc->sc_subunits);
return (0);
}
void
ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc)
{
int subunit;
if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED))
return;
destroy_dev(sc->sc_cdev);
destroy_dev(sc->sc_cdev_init);
destroy_dev(sc->sc_cdev_lock);
destroy_dev(sc->sc_cdev2);
destroy_dev(sc->sc_cdev2_init);
destroy_dev(sc->sc_cdev2_lock);
if (sc->sc_tty)
lwkt_gettoken(&sc->sc_tty->t_token);
if (ssc->sc_sysctl_ttyname != NULL) {
sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0);
ssc->sc_sysctl_ttyname = NULL;
}
if (ssc->sc_sysctl_ttyports != NULL) {
sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0);
ssc->sc_sysctl_ttyports = NULL;
}
usb_proc_drain(&ssc->sc_tq);
for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) {
ucom_detach_tty(ssc, &sc[subunit]);
sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED;
}
}
usb_proc_free(&ssc->sc_tq);
ucom_unref(ssc);
if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS)
ucom_drain(ssc);
ssc->sc_flag &= ~UCOM_FLAG_ATTACHED;
if (sc->sc_tty)
lwkt_reltoken(&sc->sc_tty->t_token);
}
void
ucom_drain(struct ucom_super_softc *ssc)
{
lockmgr(&ucom_lock, LK_EXCLUSIVE);
while (ssc->sc_refs > 0) {
kprintf("ucom: Waiting for a TTY device to close.\n");
usb_pause_mtx(&ucom_lock, hz);
}
lockmgr(&ucom_lock, LK_RELEASE);
}
void
ucom_drain_all(void *arg)
{
lockmgr(&ucom_lock, LK_EXCLUSIVE);
while (ucom_close_refs > 0) {
kprintf("ucom: Waiting for all detached TTY "
"devices to have open fds closed.\n");
usb_pause_mtx(&ucom_lock, hz);
}
lockmgr(&ucom_lock, LK_RELEASE);
}
static int
ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
{
struct tty *tp;
char buf[32];
tp = ttymalloc(&sc->sc_tty);
lwkt_gettoken(&tp->t_token);
tp->t_sc = (void *)sc;
tp->t_oproc = ucom_start;
tp->t_param = ucom_param;
tp->t_stop = ucom_stop;
buf[0] = '\0';
if (sc->sc_callback->ucom_tty_name) {
sc->sc_callback->ucom_tty_name(sc, buf,
sizeof(buf), ssc->sc_unit, sc->sc_subunit);
}
if (buf[0] == 0) {
if (ssc->sc_subunits > 1) {
ksnprintf(buf, sizeof(buf), "%u.%u",
ssc->sc_unit, sc->sc_subunit);
} else {
ksnprintf(buf, sizeof(buf), "%u",
ssc->sc_unit);
}
}
sc->sc_cdev = make_dev(&ucom_ops, ssc->sc_unit,
UID_ROOT, GID_WHEEL, 0600, "ttyU%s", buf);
sc->sc_cdev_init = make_dev(&ucom_ops, ssc->sc_unit | CONTROL_INIT_STATE,
UID_ROOT, GID_WHEEL, 0600, "ttyiU%s", buf);
sc->sc_cdev_lock = make_dev(&ucom_ops, ssc->sc_unit | CONTROL_LOCK_STATE,
UID_ROOT, GID_WHEEL, 0600, "ttylU%s", buf);
sc->sc_cdev2 = make_dev(&ucom_ops, ssc->sc_unit | CALLOUT_MASK,
UID_UUCP, GID_DIALER, 0660, "cuaU%s", buf);
sc->sc_cdev2_init = make_dev(&ucom_ops, ssc->sc_unit | CALLOUT_MASK | CONTROL_INIT_STATE,
UID_UUCP, GID_DIALER, 0660, "cuaiU%s", buf);
sc->sc_cdev2_lock = make_dev(&ucom_ops, ssc->sc_unit | CALLOUT_MASK | CONTROL_LOCK_STATE,
UID_UUCP, GID_DIALER, 0660, "cualU%s", buf);
sc->sc_cdev->si_tty = tp;
sc->sc_cdev_init->si_tty = tp;
sc->sc_cdev_lock->si_tty = tp;
sc->sc_cdev->si_drv1 = sc;
sc->sc_cdev_init->si_drv1 = sc;
sc->sc_cdev_lock->si_drv1 = sc;
sc->sc_cdev2->si_tty = tp;
sc->sc_cdev2_init->si_tty = tp;
sc->sc_cdev2_lock->si_tty = tp;
sc->sc_cdev2->si_drv1 = sc;
sc->sc_cdev2_init->si_drv1 = sc;
sc->sc_cdev2_lock->si_drv1 = sc;
sc->sc_tty = tp;
DPRINTF("ttycreate: %s\n", buf);
if ((ucom_cons_softc == NULL) &&
(ssc->sc_unit == ucom_cons_unit) &&
(sc->sc_subunit == ucom_cons_subunit)) {
DPRINTF("unit %d subunit %d is console",
ssc->sc_unit, sc->sc_subunit);
ucom_cons_softc = sc;
#if 0
tty_init_console(tp, ucom_cons_baud);
#endif
tp->t_termios.c_ispeed = ucom_cons_baud;
tp->t_termios.c_ospeed = ucom_cons_baud;
UCOM_MTX_LOCK(ucom_cons_softc);
ucom_cons_rx_low = 0;
ucom_cons_rx_high = 0;
ucom_cons_tx_low = 0;
ucom_cons_tx_high = 0;
sc->sc_flag |= UCOM_FLAG_CONSOLE;
ucom_open(ucom_cons_softc);
ucom_param(tp, &tp->t_termios);
UCOM_MTX_UNLOCK(ucom_cons_softc);
}
lwkt_reltoken(&tp->t_token);
return (0);
}
static void
ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
{
struct tty *tp = sc->sc_tty;
DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
UCOM_MTX_LOCK(ucom_cons_softc);
ucom_close(ucom_cons_softc);
sc->sc_flag &= ~UCOM_FLAG_CONSOLE;
UCOM_MTX_UNLOCK(ucom_cons_softc);
ucom_cons_softc = NULL;
}
UCOM_MTX_LOCK(sc);
sc->sc_flag |= UCOM_FLAG_GONE;
sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY);
UCOM_MTX_UNLOCK(sc);
if (tp != NULL) {
lwkt_gettoken(&tp->t_token);
ucom_close_refs++;
UCOM_MTX_LOCK(sc);
if (tp->t_state & TS_ISOPEN) {
kprintf("device still open, forcing close\n");
(*linesw[tp->t_line].l_close)(tp, 0);
ttyclose(tp);
}
ucom_close(sc);
if (sc->sc_callback->ucom_stop_read) {
(sc->sc_callback->ucom_stop_read) (sc);
}
if (sc->sc_callback->ucom_stop_write) {
(sc->sc_callback->ucom_stop_write) (sc);
}
UCOM_MTX_UNLOCK(sc);
lwkt_reltoken(&tp->t_token);
} else {
DPRINTF("no tty\n");
}
dev_ops_remove_minor(&ucom_ops,ssc->sc_unit);
ucom_unref(ssc);
}
void
ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev)
{
char buf[64];
uint8_t iface_index;
struct usb_attach_arg *uaa;
ksnprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX
"%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits);
uaa = device_get_ivars(dev);
iface_index = uaa->info.bIfaceIndex;
if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0)
device_printf(dev, "Could not set PNP info\n");
if (ssc->sc_sysctl_ttyname == NULL) {
}
if (ssc->sc_sysctl_ttyports == NULL) {
}
}
static void
ucom_queue_command(struct ucom_softc *sc,
usb_proc_callback_t *fn, struct termios *pt,
struct usb_proc_msg *t0, struct usb_proc_msg *t1)
{
struct ucom_super_softc *ssc = sc->sc_super;
struct ucom_param_task *task;
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (usb_proc_is_gone(&ssc->sc_tq)) {
DPRINTF("proc is gone\n");
return;
}
task = (struct ucom_param_task *)
usb_proc_msignal(&ssc->sc_tq, t0, t1);
task->hdr.pm_callback = fn;
task->sc = sc;
if (pt != NULL)
task->termios_copy = *pt;
if (fn == ucom_cfg_close)
usb_proc_mwait(&ssc->sc_tq, t0, t1);
if (fn == ucom_cfg_start_transfers)
sc->sc_last_start_xfer = &task->hdr;
}
static void
ucom_shutdown(struct ucom_softc *sc)
{
struct tty *tp = sc->sc_tty;
UCOM_MTX_ASSERT(sc, MA_OWNED);
DPRINTF("\n");
if (tp->t_termios.c_cflag & HUPCL) {
ucom_modem(tp, 0, SER_DTR);
}
}
uint8_t
ucom_cfg_is_gone(struct ucom_softc *sc)
{
struct ucom_super_softc *ssc = sc->sc_super;
return (usb_proc_is_gone(&ssc->sc_tq));
}
static void
ucom_cfg_start_transfers(struct usb_proc_msg *_task)
{
struct ucom_cfg_task *task =
(struct ucom_cfg_task *)_task;
struct ucom_softc *sc = task->sc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
if (_task == sc->sc_last_start_xfer)
sc->sc_flag |= UCOM_FLAG_GP_DATA;
if (sc->sc_callback->ucom_start_read) {
(sc->sc_callback->ucom_start_read) (sc);
}
if (sc->sc_callback->ucom_start_write) {
(sc->sc_callback->ucom_start_write) (sc);
}
}
static void
ucom_start_transfers(struct ucom_softc *sc)
{
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
if (sc->sc_callback->ucom_start_read) {
(sc->sc_callback->ucom_start_read) (sc);
}
if (sc->sc_callback->ucom_start_write) {
(sc->sc_callback->ucom_start_write) (sc);
}
}
static void
ucom_cfg_open(struct usb_proc_msg *_task)
{
struct ucom_cfg_task *task =
(struct ucom_cfg_task *)_task;
struct ucom_softc *sc = task->sc;
DPRINTF("\n");
if (sc->sc_flag & UCOM_FLAG_LL_READY) {
} else {
sc->sc_flag |= UCOM_FLAG_LL_READY;
if (sc->sc_callback->ucom_cfg_open) {
(sc->sc_callback->ucom_cfg_open) (sc);
usb_pause_mtx(sc->sc_lock, hz / 10);
}
}
}
static int
ucom_dev_open(struct dev_open_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct ucom_softc *sc = (struct ucom_softc *)dev->si_drv1;
int error;
int mynor;
error = 0;
mynor = minor(dev);
UCOM_MTX_LOCK(sc);
error = ucom_open(sc);
UCOM_MTX_UNLOCK(sc);
return error;
}
static int
ucom_open(struct ucom_softc *sc)
{
int error;
struct tty *tp;
int mynor;
mynor = minor(sc->sc_cdev);
if (sc->sc_flag & UCOM_FLAG_GONE) {
return (ENXIO);
}
if (sc->sc_flag & UCOM_FLAG_HL_READY) {
return (0);
}
DPRINTF("tp = %p\n", sc->sc_tty);
if (sc->sc_callback->ucom_pre_open) {
error = (sc->sc_callback->ucom_pre_open) (sc);
if (error) {
return (error);
}
}
sc->sc_flag |= UCOM_FLAG_HL_READY;
tp = sc->sc_tty;
lwkt_gettoken(&tp->t_token);
if (!ISSET(tp->t_state, TS_ISOPEN)) {
struct termios t;
tp->t_dev = reference_dev(sc->sc_cdev);
t = mynor & CALLOUT_MASK ? sc->sc_it_out : sc->sc_it_in;
tp->t_ospeed = 0;
ucom_param(tp, &t);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
ttychars(tp);
ttsetwater(tp);
sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
sc->sc_lsr = 0;
sc->sc_msr = 0;
sc->sc_mcr = 0;
sc->sc_pls_curr = 0;
sc->sc_pls_set = 0;
sc->sc_pls_clr = 0;
sc->sc_jitterbuf_in = 0;
sc->sc_jitterbuf_out = 0;
ucom_queue_command(sc, ucom_cfg_open, NULL,
&sc->sc_open_task[0].hdr,
&sc->sc_open_task[1].hdr);
ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
&sc->sc_start_task[0].hdr,
&sc->sc_start_task[1].hdr);
ucom_modem(sc->sc_tty, SER_DTR | SER_RTS, 0);
ucom_ring(sc, 0);
ucom_break(sc, 0);
ucom_status_change(sc);
if (ISSET(sc->sc_msr, SER_DCD)) {
(*linesw[tp->t_line].l_modem)(tp, 1);
}
}
error = ttyopen(sc->sc_cdev, tp);
if (error) {
lwkt_reltoken(&tp->t_token);
return (error);
}
error = (*linesw[tp->t_line].l_open)(sc->sc_cdev, tp);
if (error) {
lwkt_reltoken(&tp->t_token);
return (error);
}
disc_optim(tp, &tp->t_termios, sc);
lwkt_reltoken(&tp->t_token);
return (0);
}
static void
ucom_cfg_close(struct usb_proc_msg *_task)
{
struct ucom_cfg_task *task =
(struct ucom_cfg_task *)_task;
struct ucom_softc *sc = task->sc;
DPRINTF("\n");
if (sc->sc_flag & UCOM_FLAG_LL_READY) {
sc->sc_flag &= ~UCOM_FLAG_LL_READY;
if (sc->sc_callback->ucom_cfg_close)
(sc->sc_callback->ucom_cfg_close) (sc);
} else {
}
}
static int
ucom_dev_close(struct dev_close_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct ucom_softc *sc = (struct ucom_softc *)dev->si_drv1;
int error;
UCOM_MTX_LOCK(sc);
error = ucom_close(sc);
UCOM_MTX_UNLOCK(sc);
return error;
}
static int
ucom_close(struct ucom_softc *sc)
{
struct tty *tp = sc->sc_tty;
int error = 0;
DPRINTF("tp=%p\n", tp);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
DPRINTF("tp=%p already closed\n", tp);
return (error);
}
if (!ISSET(tp->t_state, TS_ISOPEN)) {
return(error);
}
ucom_shutdown(sc);
ucom_queue_command(sc, ucom_cfg_close, NULL,
&sc->sc_close_task[0].hdr,
&sc->sc_close_task[1].hdr);
sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
if (sc->sc_callback->ucom_stop_read) {
(sc->sc_callback->ucom_stop_read) (sc);
}
lwkt_gettoken(&tp->t_token);
(*linesw[tp->t_line].l_close)(tp, 0);
disc_optim(tp, &tp->t_termios, sc);
ttyclose(tp);
if (tp->t_dev) {
release_dev(tp->t_dev);
tp->t_dev = NULL;
}
lwkt_reltoken(&tp->t_token);
return (error);
}
#if 0
static void
ucom_inwakeup(struct tty *tp)
{
struct ucom_softc *sc = tty_softc(tp);
uint16_t pos;
if (sc == NULL)
return;
UCOM_MTX_ASSERT(sc, MA_OWNED);
DPRINTF("tp=%p\n", tp);
if (ttydisc_can_bypass(tp) != 0 ||
(sc->sc_flag & UCOM_FLAG_HL_READY) == 0 ||
(sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) {
return;
}
sc->sc_flag |= UCOM_FLAG_INWAKEUP;
pos = sc->sc_jitterbuf_out;
while (sc->sc_jitterbuf_in != pos) {
int c;
c = (char)sc->sc_jitterbuf[pos];
if (ttydisc_rint(tp, c, 0) == -1)
break;
pos++;
if (pos >= UCOM_JITTERBUF_SIZE)
pos -= UCOM_JITTERBUF_SIZE;
}
sc->sc_jitterbuf_out = pos;
if ((sc->sc_jitterbuf_in == pos) &&
(sc->sc_flag & UCOM_FLAG_RTS_IFLOW))
ucom_rts(sc, 0);
sc->sc_flag &= ~UCOM_FLAG_INWAKEUP;
}
#endif
static int
ucom_dev_read(struct dev_read_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct ucom_softc *sc;
struct tty *tp;
int error;
sc = NULL;
tp = dev->si_tty;
lwkt_gettoken(&tp->t_token);
KKASSERT(tp!=NULL);
sc = tp->t_sc;
KKASSERT(sc!=NULL);
DPRINTF("tp = %p, flag = 0x%x\n", tp, ap->a_ioflag);
error = (*linesw[tp->t_line].l_read)(tp, ap->a_uio, ap->a_ioflag);
lwkt_reltoken(&tp->t_token);
DPRINTF("error = %d\n", error);
return (error);
}
static int
ucom_dev_write(struct dev_write_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct ucom_softc *sc;
struct tty *tp;
int error;
tp = dev->si_tty;
lwkt_gettoken(&tp->t_token);
KKASSERT(tp!=NULL);
sc = tp->t_sc;
KKASSERT(sc!=NULL);
DPRINTF("tp = %p, flag = 0x%x\n", tp, ap->a_ioflag);
error = (*linesw[tp->t_line].l_write)(tp, ap->a_uio, ap->a_ioflag);
lwkt_reltoken(&tp->t_token);
DPRINTF("ucomwrite: error = %d\n", error);
return (error);
}
static int
ucom_dev_ioctl(struct dev_ioctl_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct ucom_softc *sc = (struct ucom_softc *)dev->si_drv1;
u_long cmd = ap->a_cmd;
caddr_t data = ap->a_data;
struct tty *tp = sc->sc_tty;
int d;
int error;
int mynor;
UCOM_MTX_LOCK(sc);
lwkt_gettoken(&tp->t_token);
mynor = minor(dev);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (EIO);
}
DPRINTF("cmd = 0x%08lx\n", cmd);
if (mynor & CONTROL_MASK) {
struct termios *ct;
switch (mynor & CONTROL_MASK) {
case CONTROL_INIT_STATE:
ct = mynor & CALLOUT_MASK ? &sc->sc_it_out : &sc->sc_it_in;
break;
case CONTROL_LOCK_STATE:
ct = mynor & CALLOUT_MASK ? &sc->sc_lt_out : &sc->sc_lt_in;
break;
default:
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (ENODEV);
}
switch (ap->a_cmd) {
case TIOCSETA:
error = caps_priv_check(ap->a_cred,
SYSCAP_RESTRICTEDROOT);
if (error != 0) {
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (error);
}
*ct = *(struct termios *)data;
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (0);
case TIOCGETA:
*(struct termios *)data = *ct;
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (0);
case TIOCGETD:
*(int *)data = TTYDISC;
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (0);
case TIOCGWINSZ:
bzero(data, sizeof(struct winsize));
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (0);
default:
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (ENOTTY);
}
}
error = (*linesw[tp->t_line].l_ioctl)(tp, ap->a_cmd, ap->a_data,
ap->a_fflag, ap->a_cred);
if (error != ENOIOCTL) {
DPRINTF("ucomioctl: l_ioctl: error = %d\n", error);
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (error);
}
error = ttioctl(tp, ap->a_cmd, ap->a_data, ap->a_fflag);
disc_optim(tp, &tp->t_termios, sc);
if (error != ENOIOCTL) {
DPRINTF("ucomioctl: ttioctl: error = %d\n", error);
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (error);
}
error = 0;
switch (cmd) {
#if 0
case TIOCSRING:
ucom_ring(sc, 1);
error = 0;
break;
case TIOCCRING:
ucom_ring(sc, 0);
error = 0;
break;
#endif
case TIOCSBRK:
ucom_break(sc, 1);
error = 0;
break;
case TIOCCBRK:
ucom_break(sc, 0);
error = 0;
break;
case TIOCSDTR:
ucom_dtr(sc, 1);
break;
case TIOCCDTR:
ucom_dtr(sc, 0);
break;
case TIOCMSET:
d = *(int *)ap->a_data;
DPRINTF("ucomioctl: TIOCMSET, 0x%x\n", d);
ucom_modem(tp, ucom_fromtio(d), 0);
break;
case TIOCMGET:
d = ucom_modem(tp, 0, 0);
DPRINTF("ucomioctl: TIOCMGET, 0x%x\n", d);
*(int *)ap->a_data = ucom_totio(d);
ucom_status_change(sc);
break;
case TIOCMBIS:
d = *(int *)ap->a_data;
ucom_modem(tp, ucom_fromtio(d), 0);
break;
case TIOCMBIC:
d = *(int *)ap->a_data;
ucom_modem(tp, 0, ucom_fromtio(d));
break;
default:
if (sc->sc_callback->ucom_ioctl) {
error = (sc->sc_callback->ucom_ioctl)
(sc, cmd, data, 0, curthread);
if (error>=0) {
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return(error);
}
} else {
error = ENOIOCTL;
}
if (error == ENOIOCTL)
error = pps_ioctl(cmd, data, &sc->sc_pps);
break;
}
lwkt_reltoken(&tp->t_token);
UCOM_MTX_UNLOCK(sc);
return (error);
}
static int
ucom_totio(int bits)
{
int rbits = 0;
SET(bits, TIOCM_LE);
if (ISSET(bits, SER_DTR)) {
SET(rbits, TIOCM_DTR);
}
if (ISSET(bits, SER_RTS)) {
SET(rbits, TIOCM_RTS);
}
if (ISSET(bits, SER_CTS)) {
SET(rbits, TIOCM_CTS);
}
if (ISSET(bits, SER_DCD)) {
SET(rbits, TIOCM_CD);
}
if (ISSET(bits, SER_DSR)) {
SET(rbits, TIOCM_DSR);
}
if (ISSET(bits, SER_RI)) {
SET(rbits, TIOCM_RI);
}
return (rbits);
}
static int
ucom_fromtio(int bits)
{
int rbits = 0;
if (ISSET(bits, TIOCM_DTR)) {
SET(rbits, SER_DTR);
}
if (ISSET(bits, TIOCM_RTS)) {
SET(rbits, SER_RTS);
}
if (ISSET(bits, TIOCM_CTS)) {
SET(rbits, SER_CTS);
}
if (ISSET(bits, TIOCM_CD)) {
SET(rbits, SER_DCD);
}
if (ISSET(bits, TIOCM_DSR)) {
SET(rbits, SER_DSR);
}
if (ISSET(bits, TIOCM_RI)) {
SET(rbits, SER_RI);
}
return (rbits);
}
static int
ucom_modem(struct tty *tp, int sigon, int sigoff)
{
struct ucom_softc *sc = (struct ucom_softc *)tp->t_sc;
uint8_t onoff;
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return (0);
}
if ((sigon == 0) && (sigoff == 0)) {
if (sc->sc_mcr & SER_DTR) {
sigon |= SER_DTR;
}
if (sc->sc_mcr & SER_RTS) {
sigon |= SER_RTS;
}
if (sc->sc_msr & SER_CTS) {
sigon |= SER_CTS;
}
if (sc->sc_msr & SER_DCD) {
sigon |= SER_DCD;
}
if (sc->sc_msr & SER_DSR) {
sigon |= SER_DSR;
}
if (sc->sc_msr & SER_RI) {
sigon |= SER_RI;
}
return (sigon);
}
if (sigon & SER_DTR) {
sc->sc_mcr |= SER_DTR;
}
if (sigoff & SER_DTR) {
sc->sc_mcr &= ~SER_DTR;
}
if (sigon & SER_RTS) {
sc->sc_mcr |= SER_RTS;
}
if (sigoff & SER_RTS) {
sc->sc_mcr &= ~SER_RTS;
}
onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
ucom_dtr(sc, onoff);
onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
ucom_rts(sc, onoff);
return (0);
}
static void
ucom_cfg_line_state(struct usb_proc_msg *_task)
{
struct ucom_cfg_task *task =
(struct ucom_cfg_task *)_task;
struct ucom_softc *sc = task->sc;
uint8_t notch_bits;
uint8_t any_bits;
uint8_t prev_value;
uint8_t last_value;
uint8_t mask;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
mask = 0;
if (sc->sc_callback->ucom_cfg_set_dtr)
mask |= UCOM_LS_DTR;
if (sc->sc_callback->ucom_cfg_set_rts)
mask |= UCOM_LS_RTS;
if (sc->sc_callback->ucom_cfg_set_break)
mask |= UCOM_LS_BREAK;
if (sc->sc_callback->ucom_cfg_set_ring)
mask |= UCOM_LS_RING;
notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
prev_value = sc->sc_pls_curr ^ notch_bits;
last_value = sc->sc_pls_curr;
sc->sc_pls_curr = 0;
sc->sc_pls_set = 0;
sc->sc_pls_clr = 0;
if (notch_bits & UCOM_LS_DTR)
sc->sc_callback->ucom_cfg_set_dtr(sc,
(prev_value & UCOM_LS_DTR) ? 1 : 0);
if (notch_bits & UCOM_LS_RTS)
sc->sc_callback->ucom_cfg_set_rts(sc,
(prev_value & UCOM_LS_RTS) ? 1 : 0);
if (notch_bits & UCOM_LS_BREAK)
sc->sc_callback->ucom_cfg_set_break(sc,
(prev_value & UCOM_LS_BREAK) ? 1 : 0);
if (notch_bits & UCOM_LS_RING)
sc->sc_callback->ucom_cfg_set_ring(sc,
(prev_value & UCOM_LS_RING) ? 1 : 0);
if (any_bits & UCOM_LS_DTR)
sc->sc_callback->ucom_cfg_set_dtr(sc,
(last_value & UCOM_LS_DTR) ? 1 : 0);
if (any_bits & UCOM_LS_RTS)
sc->sc_callback->ucom_cfg_set_rts(sc,
(last_value & UCOM_LS_RTS) ? 1 : 0);
if (any_bits & UCOM_LS_BREAK)
sc->sc_callback->ucom_cfg_set_break(sc,
(last_value & UCOM_LS_BREAK) ? 1 : 0);
if (any_bits & UCOM_LS_RING)
sc->sc_callback->ucom_cfg_set_ring(sc,
(last_value & UCOM_LS_RING) ? 1 : 0);
}
static void
ucom_line_state(struct ucom_softc *sc,
uint8_t set_bits, uint8_t clear_bits)
{
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
sc->sc_pls_curr |= set_bits;
sc->sc_pls_curr &= ~clear_bits;
sc->sc_pls_set |= set_bits;
sc->sc_pls_clr |= clear_bits;
ucom_queue_command(sc, ucom_cfg_line_state, NULL,
&sc->sc_line_state_task[0].hdr,
&sc->sc_line_state_task[1].hdr);
}
static void
ucom_ring(struct ucom_softc *sc, uint8_t onoff)
{
DPRINTF("onoff = %d\n", onoff);
if (onoff)
ucom_line_state(sc, UCOM_LS_RING, 0);
else
ucom_line_state(sc, 0, UCOM_LS_RING);
}
static void
ucom_break(struct ucom_softc *sc, uint8_t onoff)
{
DPRINTF("onoff = %d\n", onoff);
if (onoff)
ucom_line_state(sc, UCOM_LS_BREAK, 0);
else
ucom_line_state(sc, 0, UCOM_LS_BREAK);
}
static void
ucom_dtr(struct ucom_softc *sc, uint8_t onoff)
{
DPRINTF("onoff = %d\n", onoff);
if (onoff)
ucom_line_state(sc, UCOM_LS_DTR, 0);
else
ucom_line_state(sc, 0, UCOM_LS_DTR);
}
static void
ucom_rts(struct ucom_softc *sc, uint8_t onoff)
{
DPRINTF("onoff = %d\n", onoff);
if (onoff)
ucom_line_state(sc, UCOM_LS_RTS, 0);
else
ucom_line_state(sc, 0, UCOM_LS_RTS);
}
static void
ucom_cfg_status_change(struct usb_proc_msg *_task)
{
struct ucom_cfg_task *task =
(struct ucom_cfg_task *)_task;
struct ucom_softc *sc = task->sc;
struct tty *tp;
uint8_t new_msr;
uint8_t new_lsr;
uint8_t msr_delta;
uint8_t lsr_delta;
tp = sc->sc_tty;
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (sc->sc_callback->ucom_cfg_get_status == NULL) {
return;
}
new_msr = 0;
new_lsr = 0;
(sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
msr_delta = (sc->sc_msr ^ new_msr);
lsr_delta = (sc->sc_lsr ^ new_lsr);
sc->sc_msr = new_msr;
sc->sc_lsr = new_lsr;
#if 0
switch(ucom_pps_mode) {
case 1:
if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) &&
(msr_delta & SER_CTS)) {
pps_capture(&sc->sc_pps);
pps_event(&sc->sc_pps, (sc->sc_msr & SER_CTS) ?
PPS_CAPTURECLEAR : PPS_CAPTUREASSERT);
}
break;
case 2:
if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) &&
(msr_delta & SER_DCD)) {
pps_capture(&sc->sc_pps);
pps_event(&sc->sc_pps, (sc->sc_msr & SER_DCD) ?
PPS_CAPTURECLEAR : PPS_CAPTUREASSERT);
}
break;
default:
break;
}
#endif
if (msr_delta & SER_DCD) {
int onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
DPRINTF("DCD changed to %d\n", onoff);
(*linesw[tp->t_line].l_modem)(tp, onoff);
}
if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) {
DPRINTF("BREAK detected\n");
(*linesw[tp->t_line].l_rint)(0, tp);
}
if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) {
DPRINTF("Frame error detected\n");
(*linesw[tp->t_line].l_rint)(0, tp);
}
if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) {
DPRINTF("Parity error detected\n");
(*linesw[tp->t_line].l_rint)(0, tp);
}
}
void
ucom_status_change(struct ucom_softc *sc)
{
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (sc->sc_flag & UCOM_FLAG_CONSOLE)
return;
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("\n");
ucom_queue_command(sc, ucom_cfg_status_change, NULL,
&sc->sc_status_task[0].hdr,
&sc->sc_status_task[1].hdr);
}
static void
ucom_cfg_param(struct usb_proc_msg *_task)
{
struct ucom_param_task *task =
(struct ucom_param_task *)_task;
struct ucom_softc *sc = task->sc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (sc->sc_callback->ucom_cfg_param == NULL) {
return;
}
(sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy);
usb_pause_mtx(sc->sc_lock, hz / 10);
}
static int
ucom_param(struct tty *tp, struct termios *t)
{
struct ucom_softc *sc = (struct ucom_softc *)tp->t_sc;
uint8_t opened;
int error;
lwkt_gettoken(&tp->t_token);
UCOM_MTX_ASSERT(sc, MA_OWNED);
opened = 0;
error = 0;
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
error = ucom_open(sc);
if (error) {
goto done;
}
opened = 1;
}
DPRINTF("sc = %p\n", sc);
if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
DPRINTF("mismatch ispeed and ospeed\n");
error = EINVAL;
goto done;
}
t->c_ispeed = t->c_ospeed;
if (sc->sc_callback->ucom_pre_param) {
error = (sc->sc_callback->ucom_pre_param) (sc, t);
if (error) {
DPRINTF("callback error = %d\n", error);
goto done;
}
}
sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
ucom_queue_command(sc, ucom_cfg_param, t,
&sc->sc_param_task[0].hdr,
&sc->sc_param_task[1].hdr);
ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
&sc->sc_start_task[0].hdr,
&sc->sc_start_task[1].hdr);
if (t->c_cflag & CRTS_IFLOW) {
sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
} else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
ucom_modem(tp, SER_RTS, 0);
}
done:
if (error) {
if (opened) {
ucom_close(sc);
}
}
lwkt_reltoken(&tp->t_token);
return (error);
}
static void
ucom_start(struct tty *tp)
{
struct ucom_softc *sc = (struct ucom_softc *)tp->t_sc;
int didlock;
if (lockowned(sc->sc_lock)) {
didlock = 0;
} else {
UCOM_MTX_LOCK(sc);
didlock = 1;
}
DPRINTF("sc = %p\n", sc);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
if (didlock)
UCOM_MTX_UNLOCK(sc);
return;
}
lwkt_gettoken(&tp->t_token);
if (tp->t_state & TS_TBLOCK) {
if (ISSET(sc->sc_mcr, SER_RTS) &&
ISSET(sc->sc_flag, UCOM_FLAG_RTS_IFLOW)) {
DPRINTF("ucom_start: clear RTS\n");
(void)ucom_modem(tp, 0, SER_RTS);
}
} else {
if (!ISSET(sc->sc_mcr, SER_RTS) &&
tp->t_rawq.c_cc <= tp->t_ilowat &&
ISSET(sc->sc_flag, UCOM_FLAG_RTS_IFLOW)) {
DPRINTF("ucom_start: set RTS\n");
(void)ucom_modem(tp, SER_RTS, 0);
}
}
if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) {
ttwwakeup(tp);
DPRINTF("ucom_start: stopped\n");
goto out;
}
if (tp->t_outq.c_cc <= tp->t_olowat) {
if (ISSET(tp->t_state, TS_SO_OLOWAT)) {
CLR(tp->t_state, TS_SO_OLOWAT);
wakeup(TSA_OLOWAT(tp));
}
KNOTE(&tp->t_wkq.ki_note, 0);
if (tp->t_outq.c_cc == 0) {
if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) ==
TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) {
CLR(tp->t_state, TS_SO_OCOMPLETE);
wakeup(TSA_OCOMPLETE(tp));
}
goto out;
}
}
DPRINTF("about to start write?\n");
ucom_start_transfers(sc);
ttwwakeup(tp);
out:
lwkt_reltoken(&tp->t_token);
if (didlock)
UCOM_MTX_UNLOCK(sc);
}
static void
ucom_stop(struct tty *tp, int flag)
{
struct ucom_softc *sc = (struct ucom_softc *)tp->t_sc;
DPRINTF("sc = %p, x = 0x%x\n", sc, flag);
lwkt_gettoken(&tp->t_token);
if (flag & FREAD) {
DPRINTF("read\n");
if (sc->sc_callback->ucom_stop_read) {
(sc->sc_callback->ucom_stop_read) (sc);
}
if (sc->sc_callback->ucom_start_read) {
(sc->sc_callback->ucom_start_read) (sc);
}
#if 0
ucomstopread(sc);
ucomstartread(sc);
#endif
}
if (flag & FWRITE) {
DPRINTF("write\n");
if (ISSET(tp->t_state, TS_BUSY)) {
if (!ISSET(tp->t_state, TS_TTSTOP))
SET(tp->t_state, TS_FLUSH);
}
}
DPRINTF("done\n");
lwkt_reltoken(&tp->t_token);
}
uint8_t
ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
uint32_t offset, uint32_t len, uint32_t *actlen)
{
struct usb_page_search res;
struct tty *tp = sc->sc_tty;
uint32_t cnt;
uint32_t offset_orig;
DPRINTF("\n");
UCOM_MTX_ASSERT(sc, MA_OWNED);
if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
unsigned int temp;
temp = ucom_cons_tx_high - ucom_cons_tx_low;
temp %= UCOM_CONS_BUFSIZE;
if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low))
temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low);
if (temp > len)
temp = len;
usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp);
ucom_cons_tx_low += temp;
ucom_cons_tx_low %= UCOM_CONS_BUFSIZE;
*actlen = temp;
return (temp ? 1 : 0);
}
if (tty_gone(tp) ||
!(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
actlen[0] = 0;
return (0);
}
offset_orig = offset;
lwkt_gettoken(&tp->t_token);
while (len != 0) {
usbd_get_page(pc, offset, &res);
if (res.length > len) {
res.length = len;
}
SET(tp->t_state, TS_BUSY);
cnt = clist_qtob(&tp->t_outq, res.buffer, len);
if (cnt == 0) {
DPRINTF("ucom_get_data: cnt == 0\n");
CLR(tp->t_state, TS_BUSY);
break;
}
CLR(tp->t_state, TS_BUSY);
offset += cnt;
len -= cnt;
if (cnt < res.length) {
break;
}
}
lwkt_reltoken(&tp->t_token);
actlen[0] = offset - offset_orig;
DPRINTF("cnt=%d\n", actlen[0]);
if (actlen[0] == 0) {
return (0);
}
return (1);
}
void
ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
uint32_t offset, uint32_t len)
{
struct usb_page_search res;
struct tty *tp = sc->sc_tty;
char *buf;
uint32_t cnt;
int lostcc;
DPRINTF("\n");
UCOM_MTX_ASSERT(sc, MA_OWNED);
lwkt_gettoken(&tp->t_token);
if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
unsigned int temp;
temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low;
temp %= UCOM_CONS_BUFSIZE;
if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high))
temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high);
if (temp > len)
temp = len;
usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp);
ucom_cons_rx_high += temp;
ucom_cons_rx_high %= UCOM_CONS_BUFSIZE;
lwkt_reltoken(&tp->t_token);
return;
}
if (tty_gone(tp)) {
lwkt_reltoken(&tp->t_token);
return;
}
if (len == 0) {
lwkt_reltoken(&tp->t_token);
return;
}
while (len > 0) {
usbd_get_page(pc, offset, &res);
if (res.length > len) {
res.length = len;
}
len -= res.length;
offset += res.length;
buf = res.buffer;
cnt = res.length;
if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
sc->sc_jitterbuf_in = 0;
sc->sc_jitterbuf_out = 0;
if (tp->t_rawq.c_cc + cnt > tp->t_ihiwat
&& (sc->sc_flag & UCOM_FLAG_RTS_IFLOW
|| tp->t_iflag & IXOFF)
&& !(tp->t_state & TS_TBLOCK))
ttyblock(tp);
lostcc = clist_btoq((char *)buf, cnt, &tp->t_rawq);
tp->t_rawcc += cnt;
if (sc->hotchar) {
while (cnt) {
if (*buf == sc->hotchar)
break;
--cnt;
++buf;
}
if (cnt)
setsofttty();
}
ttwakeup(tp);
if (tp->t_state & TS_TTSTOP
&& (tp->t_iflag & IXANY
|| tp->t_cc[VSTART] == tp->t_cc[VSTOP])) {
tp->t_state &= ~TS_TTSTOP;
tp->t_lflag &= ~FLUSHO;
ucom_start(tp);
}
if (lostcc > 0)
kprintf("lost %d chars\n", lostcc);
continue;
} else {
for (cnt = 0; cnt != res.length; cnt++) {
if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out ||
(*linesw[tp->t_line].l_rint)((unsigned char)buf[cnt], tp) == -1) {
uint16_t end;
uint16_t pos;
pos = sc->sc_jitterbuf_in;
end = sc->sc_jitterbuf_out +
UCOM_JITTERBUF_SIZE - 1;
if (end >= UCOM_JITTERBUF_SIZE)
end -= UCOM_JITTERBUF_SIZE;
for (; cnt != res.length; cnt++) {
if (pos == end)
break;
sc->sc_jitterbuf[pos] = buf[cnt];
pos++;
if (pos >= UCOM_JITTERBUF_SIZE)
pos -= UCOM_JITTERBUF_SIZE;
}
sc->sc_jitterbuf_in = pos;
if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)
ucom_rts(sc, 1);
DPRINTF("tp=%p, lost %d "
"chars\n", tp, res.length - cnt);
break;
}
}
}
}
lwkt_reltoken(&tp->t_token);
}
void
ucom_ref(struct ucom_super_softc *ssc)
{
lockmgr(&ucom_lock, LK_EXCLUSIVE);
ssc->sc_refs++;
lockmgr(&ucom_lock, LK_RELEASE);
}
static void
ucom_free_unit(struct ucom_super_softc *ssc)
{
if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT))
return;
ucom_unit_free(ssc->sc_unit);
ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT;
}
int
ucom_unref(struct ucom_super_softc *ssc)
{
int retval;
lockmgr(&ucom_lock, LK_EXCLUSIVE);
retval = (ssc->sc_refs < 2);
ssc->sc_refs--;
lockmgr(&ucom_lock, LK_RELEASE);
if (retval)
ucom_free_unit(ssc);
return (retval);
}
static void
disc_optim(struct tty *tp, struct termios *t, struct ucom_softc *sc)
{
ASSERT_LWKT_TOKEN_HELD(&tp->t_token);
if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON))
&& (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK))
&& (!(t->c_iflag & PARMRK)
|| (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))
&& !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN))
&& linesw[tp->t_line].l_rint == ttyinput) {
DPRINTF("disc_optim: bypass l_rint\n");
tp->t_state |= TS_CAN_BYPASS_L_RINT;
} else {
DPRINTF("disc_optim: can't bypass l_rint\n");
tp->t_state &= ~TS_CAN_BYPASS_L_RINT;
}
sc->hotchar = linesw[tp->t_line].l_hotchar;
}
#if defined(GDB)
#include <gdb/gdb.h>
static gdb_probe_f ucom_gdbprobe;
static gdb_init_f ucom_gdbinit;
static gdb_term_f ucom_gdbterm;
static gdb_getc_f ucom_gdbgetc;
static gdb_putc_f ucom_gdbputc;
GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc);
static int
ucom_gdbprobe(void)
{
return ((ucom_cons_softc != NULL) ? 0 : -1);
}
static void
ucom_gdbinit(void)
{
}
static void
ucom_gdbterm(void)
{
}
static void
ucom_gdbputc(int c)
{
ucom_cnputc(NULL, c);
}
static int
ucom_gdbgetc(void)
{
return (ucom_cngetc(NULL));
}
#endif