opnsense-src/sys/dev/usb2/serial/usb2_serial.c
Andrew Thompson 62005a2d11 MFp4: //depot/projects/usb@155917
Remove dependancy towards the USB config thread in
	the USB serial core. Use USB process msignalling
	instead. Saves a little memory and hopefully makes
	the code more understandable.

Submitted by: Hans Petter Selasky
2009-01-13 19:03:47 +00:00

1195 lines
27 KiB
C

/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */
/*-
* Copyright (c) 2001-2003, 2005, 2008
* Shunsuke Akiyama <akiyama@jp.FreeBSD.org>.
* All rights reserved.
*
* 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 AUTHOR 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 AUTHOR 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*-
* Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Lennart Augustsson (lennart@augustsson.net) 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.
*/
/*
* NOTE: all function names beginning like "usb2_com_cfg_" can only
* be called from within the config thread function !
*/
#include <dev/usb2/include/usb2_standard.h>
#include <dev/usb2/include/usb2_mfunc.h>
#include <dev/usb2/include/usb2_error.h>
#include <dev/usb2/include/usb2_cdc.h>
#define USB_DEBUG_VAR usb2_com_debug
#include <dev/usb2/core/usb2_core.h>
#include <dev/usb2/core/usb2_debug.h>
#include <dev/usb2/core/usb2_process.h>
#include <dev/usb2/core/usb2_request.h>
#include <dev/usb2/core/usb2_busdma.h>
#include <dev/usb2/core/usb2_util.h>
#include <dev/usb2/serial/usb2_serial.h>
#if USB_DEBUG
static int usb2_com_debug = 0;
SYSCTL_NODE(_hw_usb2, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
SYSCTL_INT(_hw_usb2_ucom, OID_AUTO, debug, CTLFLAG_RW,
&usb2_com_debug, 0, "ucom debug level");
#endif
static usb2_proc_callback_t usb2_com_cfg_start_transfers;
static usb2_proc_callback_t usb2_com_cfg_open;
static usb2_proc_callback_t usb2_com_cfg_close;
static usb2_proc_callback_t usb2_com_cfg_break_on;
static usb2_proc_callback_t usb2_com_cfg_break_off;
static usb2_proc_callback_t usb2_com_cfg_dtr_on;
static usb2_proc_callback_t usb2_com_cfg_dtr_off;
static usb2_proc_callback_t usb2_com_cfg_rts_on;
static usb2_proc_callback_t usb2_com_cfg_rts_off;
static usb2_proc_callback_t usb2_com_cfg_status_change;
static usb2_proc_callback_t usb2_com_cfg_param;
static uint8_t usb2_com_units_alloc(uint32_t, uint32_t *);
static void usb2_com_units_free(uint32_t, uint32_t);
static int usb2_com_attach_sub(struct usb2_com_softc *);
static void usb2_com_detach_sub(struct usb2_com_softc *);
static void usb2_com_queue_command(struct usb2_com_softc *sc,
uint8_t cmd);
static void usb2_com_wait_command(struct usb2_com_softc *sc,
uint8_t cmd);
static void usb2_com_shutdown(struct usb2_com_softc *);
static void usb2_com_start_transfers(struct usb2_com_softc *);
static void usb2_com_break(struct usb2_com_softc *, uint8_t);
static void usb2_com_dtr(struct usb2_com_softc *, uint8_t);
static void usb2_com_rts(struct usb2_com_softc *, uint8_t);
static tsw_open_t usb2_com_open;
static tsw_close_t usb2_com_close;
static tsw_ioctl_t usb2_com_ioctl;
static tsw_modem_t usb2_com_modem;
static tsw_param_t usb2_com_param;
static tsw_outwakeup_t usb2_com_start_write;
static tsw_free_t usb2_com_free;
static struct ttydevsw usb2_com_class = {
.tsw_flags = TF_INITLOCK | TF_CALLOUT,
.tsw_open = usb2_com_open,
.tsw_close = usb2_com_close,
.tsw_outwakeup = usb2_com_start_write,
.tsw_ioctl = usb2_com_ioctl,
.tsw_param = usb2_com_param,
.tsw_modem = usb2_com_modem,
.tsw_free = usb2_com_free,
};
MODULE_DEPEND(usb2_serial, usb2_core, 1, 1, 1);
MODULE_VERSION(usb2_serial, 1);
#define UCOM_UNIT_MAX 0x1000 /* exclusive */
#define UCOM_SUB_UNIT_MAX 0x100 /* exclusive */
static uint8_t usb2_com_bitmap[(UCOM_UNIT_MAX + 7) / 8];
static uint8_t
usb2_com_units_alloc(uint32_t sub_units, uint32_t *p_root_unit)
{
uint32_t n;
uint32_t o;
uint32_t x;
uint32_t max = UCOM_UNIT_MAX - (UCOM_UNIT_MAX % sub_units);
uint8_t error = 1;
mtx_lock(&Giant);
for (n = 0; n < max; n += sub_units) {
/* check for free consecutive bits */
for (o = 0; o < sub_units; o++) {
x = n + o;
if (usb2_com_bitmap[x / 8] & (1 << (x % 8))) {
goto skip;
}
}
/* allocate */
for (o = 0; o < sub_units; o++) {
x = n + o;
usb2_com_bitmap[x / 8] |= (1 << (x % 8));
}
error = 0;
break;
skip: ;
}
mtx_unlock(&Giant);
/*
* Always set the variable pointed to by "p_root_unit" so that
* the compiler does not think that it is used uninitialised:
*/
*p_root_unit = n;
return (error);
}
static void
usb2_com_units_free(uint32_t root_unit, uint32_t sub_units)
{
uint32_t x;
mtx_lock(&Giant);
while (sub_units--) {
x = root_unit + sub_units;
usb2_com_bitmap[x / 8] &= ~(1 << (x % 8));
}
mtx_unlock(&Giant);
}
/*
* "N" sub_units are setup at a time. All sub-units will
* be given sequential unit numbers. The number of
* sub-units can be used to differentiate among
* different types of devices.
*
* The mutex pointed to by "p_mtx" is applied before all
* callbacks are called back. Also "p_mtx" must be applied
* before calling into the ucom-layer! Currently only Giant
* is supported.
*/
int
usb2_com_attach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc,
uint32_t sub_units, void *parent,
const struct usb2_com_callback *callback, struct mtx *p_mtx)
{
uint32_t n;
uint32_t root_unit;
int error = 0;
if ((sc == NULL) ||
(sub_units == 0) ||
(sub_units > UCOM_SUB_UNIT_MAX) ||
(callback == NULL)) {
return (EINVAL);
}
if (usb2_com_units_alloc(sub_units, &root_unit)) {
return (ENOMEM);
}
if (usb2_proc_setup(&ssc->sc_config_td, p_mtx, USB_PRI_MED)) {
usb2_com_units_free(root_unit, sub_units);
return (ENOMEM);
}
for (n = 0; n < sub_units; n++, sc++) {
sc->sc_unit = root_unit + n;
sc->sc_local_unit = n;
sc->sc_super = ssc;
sc->sc_parent_mtx = p_mtx;
sc->sc_parent = parent;
sc->sc_callback = callback;
error = usb2_com_attach_sub(sc);
if (error) {
usb2_com_detach(ssc, sc - n, n);
usb2_com_units_free(root_unit + n, sub_units - n);
break;
}
sc->sc_flag |= UCOM_FLAG_ATTACHED;
}
return (error);
}
/* NOTE: the following function will do nothing if
* the structure pointed to by "ssc" and "sc" is zero.
*/
void
usb2_com_detach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc,
uint32_t sub_units)
{
uint32_t n;
usb2_proc_drain(&ssc->sc_config_td);
for (n = 0; n < sub_units; n++, sc++) {
if (sc->sc_flag & UCOM_FLAG_ATTACHED) {
usb2_com_detach_sub(sc);
usb2_com_units_free(sc->sc_unit, 1);
/* avoid duplicate detach: */
sc->sc_flag &= ~UCOM_FLAG_ATTACHED;
}
}
usb2_proc_unsetup(&ssc->sc_config_td);
}
static int
usb2_com_attach_sub(struct usb2_com_softc *sc)
{
struct tty *tp;
int error = 0;
uint8_t n;
char buf[32]; /* temporary TTY device name buffer */
tp = tty_alloc(&usb2_com_class, sc, sc->sc_parent_mtx);
if (tp == NULL) {
error = ENOMEM;
goto done;
}
DPRINTF("tp = %p, unit = %d\n", tp, sc->sc_unit);
buf[0] = 0; /* set some default value */
/* Check if the client has a custom TTY name */
if (sc->sc_callback->usb2_com_tty_name) {
sc->sc_callback->usb2_com_tty_name(sc, buf,
sizeof(buf), sc->sc_local_unit);
}
if (buf[0] == 0) {
/* Use default TTY name */
if (snprintf(buf, sizeof(buf), "U%u", sc->sc_unit)) {
/* ignore */
}
}
tty_makedev(tp, NULL, "%s", buf);
sc->sc_tty = tp;
DPRINTF("ttycreate: %s\n", buf);
usb2_cv_init(&sc->sc_cv, "usb2_com");
/*
* Set all function callback pointers for deferred COM
* operations:
*/
for (n = 0; n != 2; n++) {
sc->sc_cmds[(2*USB_COM_CFG_START_TRANSFERS) + n].hdr.pm_callback =
&usb2_com_cfg_start_transfers;
sc->sc_cmds[(2*USB_COM_CFG_OPEN) + n].hdr.pm_callback =
&usb2_com_cfg_open;
sc->sc_cmds[(2*USB_COM_CFG_CLOSE) + n].hdr.pm_callback =
&usb2_com_cfg_close;
sc->sc_cmds[(2*USB_COM_CFG_BREAK_ON) + n].hdr.pm_callback =
&usb2_com_cfg_break_on;
sc->sc_cmds[(2*USB_COM_CFG_BREAK_OFF) + n].hdr.pm_callback =
&usb2_com_cfg_break_off;
sc->sc_cmds[(2*USB_COM_CFG_DTR_ON) + n].hdr.pm_callback =
&usb2_com_cfg_dtr_on;
sc->sc_cmds[(2*USB_COM_CFG_DTR_OFF) + n].hdr.pm_callback =
&usb2_com_cfg_dtr_off;
sc->sc_cmds[(2*USB_COM_CFG_RTS_ON) + n].hdr.pm_callback =
&usb2_com_cfg_rts_on;
sc->sc_cmds[(2*USB_COM_CFG_RTS_OFF) + n].hdr.pm_callback =
&usb2_com_cfg_rts_off;
sc->sc_cmds[(2*USB_COM_CFG_STATUS_CHANGE) + n].hdr.pm_callback =
&usb2_com_cfg_status_change;
sc->sc_cmds[(2*USB_COM_CFG_PARAM) + n].hdr.pm_callback =
&usb2_com_cfg_param;
}
/* initialise all callback pointer arguments */
for (n = 0; n != (2*USB_COM_CFG_MAX); n++) {
sc->sc_cmds[n].cc_softc = sc;
}
done:
return (error);
}
static void
usb2_com_detach_sub(struct usb2_com_softc *sc)
{
struct tty *tp = sc->sc_tty;
DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
/* the config thread has been stopped when we get here */
mtx_lock(sc->sc_parent_mtx);
sc->sc_flag |= UCOM_FLAG_GONE;
sc->sc_flag &= ~(UCOM_FLAG_HL_READY |
UCOM_FLAG_LL_READY);
mtx_unlock(sc->sc_parent_mtx);
if (tp) {
tty_lock(tp);
usb2_com_close(tp); /* close, if any */
tty_rel_gone(tp);
mtx_lock(sc->sc_parent_mtx);
/* Wait for the callback after the TTY is torn down */
while (sc->sc_ttyfreed == 0)
usb2_cv_wait(&sc->sc_cv, sc->sc_parent_mtx);
/*
* make sure that read and write transfers are stopped
*/
if (sc->sc_callback->usb2_com_stop_read) {
(sc->sc_callback->usb2_com_stop_read) (sc);
}
if (sc->sc_callback->usb2_com_stop_write) {
(sc->sc_callback->usb2_com_stop_write) (sc);
}
mtx_unlock(sc->sc_parent_mtx);
}
usb2_cv_destroy(&sc->sc_cv);
}
/*
* The following function queues a command for deferred execution.
* The following function must be called locked.
*/
static void
usb2_com_queue_command(struct usb2_com_softc *sc, uint8_t cmd)
{
struct usb2_com_super_softc *ssc = sc->sc_super;
if (usb2_proc_is_gone(&ssc->sc_config_td)) {
DPRINTF("proc is gone\n");
return; /* nothing to do */
}
if (usb2_proc_msignal(&ssc->sc_config_td,
&sc->sc_cmds[2*cmd], &sc->sc_cmds[(2*cmd)+1])) {
/* ignore */
}
}
/*
* The following function waits until a command has been executed.
* The following function must be called locked.
*/
static void
usb2_com_wait_command(struct usb2_com_softc *sc, uint8_t cmd)
{
struct usb2_com_super_softc *ssc = sc->sc_super;
if (usb2_proc_is_gone(&ssc->sc_config_td)) {
DPRINTF("proc is gone\n");
return; /* nothing to do */
}
usb2_proc_mwait(&ssc->sc_config_td,
&sc->sc_cmds[2*cmd], &sc->sc_cmds[(2*cmd)+1]);
}
static void
usb2_com_shutdown(struct usb2_com_softc *sc)
{
struct tty *tp = sc->sc_tty;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
DPRINTF("\n");
/*
* Hang up if necessary:
*/
if (tp->t_termios.c_cflag & HUPCL) {
usb2_com_modem(tp, 0, SER_DTR);
}
}
/*
* This function will sleep "timeout" system ticks.
*
* Return values:
* 0: normal delay
* else: config thread is gone
*/
uint8_t
usb2_com_cfg_sleep(struct usb2_com_softc *sc, uint32_t timeout)
{
struct usb2_com_super_softc *ssc = sc->sc_super;
uint8_t is_gone;
is_gone = usb2_proc_is_gone(&ssc->sc_config_td);
if (is_gone)
goto done; /* we are detaching */
if (timeout == 0)
timeout = 1; /* one tick is the least timeout */
mtx_unlock(sc->sc_parent_mtx);
if (pause("UCOMWAIT", timeout)) {
/* ignore */
}
mtx_lock(sc->sc_parent_mtx);
/* refresh gone status */
is_gone = usb2_proc_is_gone(&ssc->sc_config_td);
done:
return (is_gone);
}
/*
* Return values:
* 0: normal
* else: config thread is gone
*/
uint8_t
usb2_com_cfg_is_gone(struct usb2_com_softc *sc)
{
struct usb2_com_super_softc *ssc = sc->sc_super;
return (usb2_proc_is_gone(&ssc->sc_config_td));
}
static void
usb2_com_cfg_start_transfers(struct usb2_proc_msg *_cc)
{
struct usb2_com_command_msg *cc = (void *)_cc;
struct usb2_com_softc *sc;
sc = cc->cc_softc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
/* TTY device closed */
return;
}
sc->sc_flag |= UCOM_FLAG_GP_DATA;
if (sc->sc_callback->usb2_com_start_read) {
(sc->sc_callback->usb2_com_start_read) (sc);
}
if (sc->sc_callback->usb2_com_start_write) {
(sc->sc_callback->usb2_com_start_write) (sc);
}
}
static void
usb2_com_start_transfers(struct usb2_com_softc *sc)
{
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
/*
* do a direct call first, to get hardware buffers flushed
*/
if (sc->sc_callback->usb2_com_start_read) {
(sc->sc_callback->usb2_com_start_read) (sc);
}
if (sc->sc_callback->usb2_com_start_write) {
(sc->sc_callback->usb2_com_start_write) (sc);
}
if (!(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
usb2_com_queue_command(sc, USB_COM_CFG_START_TRANSFERS);
}
}
static void
usb2_com_cfg_open(struct usb2_proc_msg *_cc)
{
struct usb2_com_command_msg *cc = (void *)_cc;
struct usb2_com_softc *sc;
sc = cc->cc_softc;
DPRINTF("\n");
if (sc->sc_flag & UCOM_FLAG_LL_READY) {
/* already opened */
} else {
sc->sc_flag |= UCOM_FLAG_LL_READY;
if (sc->sc_callback->usb2_com_cfg_open) {
(sc->sc_callback->usb2_com_cfg_open) (sc);
/* wait a little */
usb2_com_cfg_sleep(sc, hz / 10);
}
}
}
static int
usb2_com_open(struct tty *tp)
{
struct usb2_com_softc *sc = tty_softc(tp);
int error;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (sc->sc_flag & UCOM_FLAG_GONE) {
return (ENXIO);
}
if (sc->sc_flag & UCOM_FLAG_HL_READY) {
/* already opened */
return (0);
}
DPRINTF("tp = %p\n", tp);
if (sc->sc_callback->usb2_com_pre_open) {
/*
* give the lower layer a chance to disallow TTY open, for
* example if the device is not present:
*/
error = (sc->sc_callback->usb2_com_pre_open) (sc);
if (error) {
return (error);
}
}
sc->sc_flag |= UCOM_FLAG_HL_READY;
/* Disable transfers */
sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
sc->sc_lsr = 0;
sc->sc_msr = 0;
sc->sc_mcr = 0;
usb2_com_queue_command(sc, USB_COM_CFG_OPEN);
usb2_com_start_transfers(sc);
usb2_com_modem(tp, SER_DTR | SER_RTS, 0);
usb2_com_break(sc, 0);
usb2_com_status_change(sc);
return (0);
}
static void
usb2_com_cfg_close(struct usb2_proc_msg *_cc)
{
struct usb2_com_command_msg *cc = (void *)_cc;
struct usb2_com_softc *sc;
sc = cc->cc_softc;
DPRINTF("\n");
if (sc->sc_flag & UCOM_FLAG_LL_READY) {
sc->sc_flag &= ~(UCOM_FLAG_LL_READY |
UCOM_FLAG_GP_DATA);
if (sc->sc_callback->usb2_com_cfg_close) {
(sc->sc_callback->usb2_com_cfg_close) (sc);
}
} else {
/* already closed */
}
}
static void
usb2_com_close(struct tty *tp)
{
struct usb2_com_softc *sc = tty_softc(tp);
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
DPRINTF("tp=%p\n", tp);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
DPRINTF("tp=%p already closed\n", tp);
return;
}
usb2_com_shutdown(sc);
/* Queue and wait for close command to complete */
usb2_com_queue_command(sc, USB_COM_CFG_CLOSE);
usb2_com_wait_command(sc, USB_COM_CFG_CLOSE);
sc->sc_flag &= ~(UCOM_FLAG_HL_READY |
UCOM_FLAG_WR_START |
UCOM_FLAG_RTS_IFLOW);
if (sc->sc_callback->usb2_com_stop_read) {
(sc->sc_callback->usb2_com_stop_read) (sc);
}
if (sc->sc_callback->usb2_com_stop_write) {
(sc->sc_callback->usb2_com_stop_write) (sc);
}
}
static int
usb2_com_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
{
struct usb2_com_softc *sc = tty_softc(tp);
int error;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return (EIO);
}
DPRINTF("cmd = 0x%08lx\n", cmd);
switch (cmd) {
case TIOCSBRK:
usb2_com_break(sc, 1);
error = 0;
break;
case TIOCCBRK:
usb2_com_break(sc, 0);
error = 0;
break;
default:
if (sc->sc_callback->usb2_com_ioctl) {
error = (sc->sc_callback->usb2_com_ioctl)
(sc, cmd, data, 0, td);
} else {
error = ENOIOCTL;
}
break;
}
return (error);
}
static int
usb2_com_modem(struct tty *tp, int sigon, int sigoff)
{
struct usb2_com_softc *sc = tty_softc(tp);
uint8_t onoff;
mtx_assert(sc->sc_parent_mtx, 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;
usb2_com_dtr(sc, onoff);
onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
usb2_com_rts(sc, onoff);
return (0);
}
static void
usb2_com_cfg_break(struct usb2_com_command_msg *cc, uint8_t onoff)
{
struct usb2_com_softc *sc;
sc = cc->cc_softc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
DPRINTF("onoff=%d\n", onoff);
if (sc->sc_callback->usb2_com_cfg_set_break) {
(sc->sc_callback->usb2_com_cfg_set_break) (sc, onoff);
}
}
static void
usb2_com_cfg_break_on(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_break((void *)_cc, 1);
}
static void
usb2_com_cfg_break_off(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_break((void *)_cc, 0);
}
static void
usb2_com_break(struct usb2_com_softc *sc, uint8_t onoff)
{
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("onoff = %d\n", onoff);
usb2_com_queue_command(sc, onoff ?
USB_COM_CFG_BREAK_ON : USB_COM_CFG_BREAK_OFF);
}
static void
usb2_com_cfg_dtr(struct usb2_com_command_msg *cc, uint8_t onoff)
{
struct usb2_com_softc *sc;
sc = cc->cc_softc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
DPRINTF("onoff=%d\n", onoff);
if (sc->sc_callback->usb2_com_cfg_set_dtr) {
(sc->sc_callback->usb2_com_cfg_set_dtr) (sc, onoff);
}
}
static void
usb2_com_cfg_dtr_on(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_dtr((void *)_cc, 1);
}
static void
usb2_com_cfg_dtr_off(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_dtr((void *)_cc, 0);
}
static void
usb2_com_dtr(struct usb2_com_softc *sc, uint8_t onoff)
{
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("onoff = %d\n", onoff);
usb2_com_queue_command(sc, onoff ?
USB_COM_CFG_DTR_ON : USB_COM_CFG_DTR_OFF);
}
static void
usb2_com_cfg_rts(struct usb2_com_command_msg *cc, uint8_t onoff)
{
struct usb2_com_softc *sc;
sc = cc->cc_softc;
DPRINTF("onoff=%d\n", onoff);
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (sc->sc_callback->usb2_com_cfg_set_rts) {
(sc->sc_callback->usb2_com_cfg_set_rts) (sc, onoff);
}
}
static void
usb2_com_cfg_rts_on(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_rts((void *)_cc, 1);
}
static void
usb2_com_cfg_rts_off(struct usb2_proc_msg *_cc)
{
usb2_com_cfg_rts((void *)_cc, 0);
}
static void
usb2_com_rts(struct usb2_com_softc *sc, uint8_t onoff)
{
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("onoff = %d\n", onoff);
usb2_com_queue_command(sc, onoff ?
USB_COM_CFG_RTS_ON : USB_COM_CFG_RTS_OFF);
}
static void
usb2_com_cfg_status_change(struct usb2_proc_msg *_cc)
{
struct usb2_com_command_msg *cc = (void *)_cc;
struct usb2_com_softc *sc;
struct tty *tp;
uint8_t new_msr;
uint8_t new_lsr;
uint8_t onoff;
sc = cc->cc_softc;
tp = sc->sc_tty;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (sc->sc_callback->usb2_com_cfg_get_status == NULL) {
return;
}
/* get status */
new_msr = 0;
new_lsr = 0;
(sc->sc_callback->usb2_com_cfg_get_status) (sc, &new_lsr, &new_msr);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
/* TTY device closed */
return;
}
onoff = ((sc->sc_msr ^ new_msr) & SER_DCD);
sc->sc_msr = new_msr;
sc->sc_lsr = new_lsr;
if (onoff) {
onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
DPRINTF("DCD changed to %d\n", onoff);
ttydisc_modem(tp, onoff);
}
}
void
usb2_com_status_change(struct usb2_com_softc *sc)
{
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
return;
}
DPRINTF("\n");
usb2_com_queue_command(sc, USB_COM_CFG_STATUS_CHANGE);
}
static void
usb2_com_cfg_param(struct usb2_proc_msg *_cc)
{
struct usb2_com_command_msg *cc = (void *)_cc;
struct usb2_com_softc *sc;
struct termios t_copy;
sc = cc->cc_softc;
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
return;
}
if (sc->sc_callback->usb2_com_cfg_param == NULL) {
return;
}
t_copy = sc->sc_termios_copy;
(sc->sc_callback->usb2_com_cfg_param) (sc, &t_copy);
/* wait a little */
usb2_com_cfg_sleep(sc, hz / 10);
}
static int
usb2_com_param(struct tty *tp, struct termios *t)
{
struct usb2_com_softc *sc = tty_softc(tp);
uint8_t opened;
int error;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
opened = 0;
error = 0;
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
/* XXX the TTY layer should call "open()" first! */
error = usb2_com_open(tp);
if (error) {
goto done;
}
opened = 1;
}
DPRINTF("sc = %p\n", sc);
/* Check requested parameters. */
if (t->c_ospeed < 0) {
DPRINTF("negative ospeed\n");
error = EINVAL;
goto done;
}
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->usb2_com_pre_param) {
/* Let the lower layer verify the parameters */
error = (sc->sc_callback->usb2_com_pre_param) (sc, t);
if (error) {
DPRINTF("callback error = %d\n", error);
goto done;
}
}
/* Make a copy of the termios parameters */
sc->sc_termios_copy = *t;
/* Disable transfers */
sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
/* Queue baud rate programming command first */
usb2_com_queue_command(sc, USB_COM_CFG_PARAM);
/* Queue transfer enable command last */
usb2_com_start_transfers(sc);
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;
usb2_com_modem(tp, SER_RTS, 0);
}
done:
if (error) {
if (opened) {
usb2_com_close(tp);
}
}
return (error);
}
static void
usb2_com_start_write(struct tty *tp)
{
struct usb2_com_softc *sc = tty_softc(tp);
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
DPRINTF("sc = %p\n", sc);
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
/* The higher layer is not ready */
return;
}
sc->sc_flag |= UCOM_FLAG_WR_START;
usb2_com_start_transfers(sc);
}
/*------------------------------------------------------------------------*
* usb2_com_get_data
*
* Return values:
* 0: No data is available.
* Else: Data is available.
*------------------------------------------------------------------------*/
uint8_t
usb2_com_get_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc,
uint32_t offset, uint32_t len, uint32_t *actlen)
{
struct usb2_page_search res;
struct tty *tp = sc->sc_tty;
uint32_t cnt;
uint32_t offset_orig;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) ||
(!(sc->sc_flag & UCOM_FLAG_GP_DATA)) ||
(!(sc->sc_flag & UCOM_FLAG_WR_START))) {
actlen[0] = 0;
return (0); /* multiport device polling */
}
offset_orig = offset;
while (len != 0) {
usb2_get_page(pc, offset, &res);
if (res.length > len) {
res.length = len;
}
/* copy data directly into USB buffer */
cnt = ttydisc_getc(tp, res.buffer, res.length);
offset += cnt;
len -= cnt;
if (cnt < res.length) {
/* end of buffer */
break;
}
}
actlen[0] = offset - offset_orig;
DPRINTF("cnt=%d\n", actlen[0]);
if (actlen[0] == 0) {
return (0);
}
return (1);
}
void
usb2_com_put_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc,
uint32_t offset, uint32_t len)
{
struct usb2_page_search res;
struct tty *tp = sc->sc_tty;
char *buf;
uint32_t cnt;
mtx_assert(sc->sc_parent_mtx, MA_OWNED);
if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) ||
(!(sc->sc_flag & UCOM_FLAG_GP_DATA))) {
return; /* multiport device polling */
}
if (len == 0)
return; /* no data */
/* set a flag to prevent recursation ? */
while (len > 0) {
usb2_get_page(pc, offset, &res);
if (res.length > len) {
res.length = len;
}
len -= res.length;
offset += res.length;
/* pass characters to tty layer */
buf = res.buffer;
cnt = res.length;
/* first check if we can pass the buffer directly */
if (ttydisc_can_bypass(tp)) {
if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
DPRINTF("tp=%p, data lost\n", tp);
}
continue;
}
/* need to loop */
for (cnt = 0; cnt != res.length; cnt++) {
if (ttydisc_rint(tp, buf[cnt], 0) == -1) {
/* XXX what should we do? */
DPRINTF("tp=%p, lost %d "
"chars\n", tp, res.length - cnt);
break;
}
}
}
ttydisc_rint_done(tp);
}
static void
usb2_com_free(void *xsc)
{
struct usb2_com_softc *sc = xsc;
mtx_lock(sc->sc_parent_mtx);
sc->sc_ttyfreed = 1;
usb2_cv_signal(&sc->sc_cv);
mtx_unlock(sc->sc_parent_mtx);
}