1128 lines
26 KiB
C
1128 lines
26 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.
|
|
*/
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usb_mfunc.h>
|
|
#include <dev/usb/usb_error.h>
|
|
#include <dev/usb/usb_cdc.h>
|
|
#include <dev/usb/usb_ioctl.h>
|
|
|
|
#define USB_DEBUG_VAR usb2_com_debug
|
|
|
|
#include <dev/usb/usb_core.h>
|
|
#include <dev/usb/usb_debug.h>
|
|
#include <dev/usb/usb_process.h>
|
|
#include <dev/usb/usb_request.h>
|
|
#include <dev/usb/usb_busdma.h>
|
|
#include <dev/usb/usb_util.h>
|
|
|
|
#include <dev/usb/serial/usb_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_line_state;
|
|
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_tty(struct usb2_com_softc *, uint32_t);
|
|
static void usb2_com_detach_tty(struct usb2_com_softc *);
|
|
static void usb2_com_queue_command(struct usb2_com_softc *,
|
|
usb2_proc_callback_t *, struct termios *pt,
|
|
struct usb2_proc_msg *t0, struct usb2_proc_msg *t1);
|
|
static void usb2_com_shutdown(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_outwakeup;
|
|
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_outwakeup,
|
|
.tsw_ioctl = usb2_com_ioctl,
|
|
.tsw_param = usb2_com_param,
|
|
.tsw_modem = usb2_com_modem,
|
|
.tsw_free = usb2_com_free,
|
|
};
|
|
|
|
MODULE_DEPEND(ucom, usb, 1, 1, 1);
|
|
MODULE_VERSION(ucom, 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 "mtx" is applied before all
|
|
* callbacks are called back. Also "mtx" must be applied
|
|
* before calling into the ucom-layer!
|
|
*/
|
|
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 *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);
|
|
}
|
|
|
|
/* XXX unit management does not really belong here */
|
|
if (usb2_com_units_alloc(sub_units, &root_unit)) {
|
|
return (ENOMEM);
|
|
}
|
|
|
|
error = usb2_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
|
|
if (error) {
|
|
usb2_com_units_free(root_unit, sub_units);
|
|
return (error);
|
|
}
|
|
|
|
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_mtx = mtx;
|
|
sc->sc_parent = parent;
|
|
sc->sc_callback = callback;
|
|
|
|
error = usb2_com_attach_tty(sc, sub_units);
|
|
if (error) {
|
|
usb2_com_detach(ssc, sc - n, n);
|
|
usb2_com_units_free(root_unit + n, sub_units - n);
|
|
return (error);
|
|
}
|
|
sc->sc_flag |= UCOM_FLAG_ATTACHED;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* 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_tq);
|
|
|
|
for (n = 0; n != sub_units; n++, sc++) {
|
|
if (sc->sc_flag & UCOM_FLAG_ATTACHED) {
|
|
|
|
usb2_com_detach_tty(sc);
|
|
|
|
usb2_com_units_free(sc->sc_unit, 1);
|
|
|
|
/* avoid duplicate detach: */
|
|
sc->sc_flag &= ~UCOM_FLAG_ATTACHED;
|
|
}
|
|
}
|
|
usb2_proc_free(&ssc->sc_tq);
|
|
}
|
|
|
|
static int
|
|
usb2_com_attach_tty(struct usb2_com_softc *sc, uint32_t sub_units)
|
|
{
|
|
struct tty *tp;
|
|
int error = 0;
|
|
char buf[32]; /* temporary TTY device name buffer */
|
|
|
|
tp = tty_alloc(&usb2_com_class, sc, sc->sc_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 (sub_units > 1) {
|
|
/* multiple modems in one */
|
|
if (snprintf(buf, sizeof(buf), "U%u.%u",
|
|
sc->sc_unit - sc->sc_local_unit,
|
|
sc->sc_local_unit)) {
|
|
/* ignore */
|
|
}
|
|
} else {
|
|
/* single modem */
|
|
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");
|
|
|
|
done:
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
usb2_com_detach_tty(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_mtx);
|
|
sc->sc_flag |= UCOM_FLAG_GONE;
|
|
sc->sc_flag &= ~(UCOM_FLAG_HL_READY |
|
|
UCOM_FLAG_LL_READY);
|
|
mtx_unlock(sc->sc_mtx);
|
|
if (tp) {
|
|
tty_lock(tp);
|
|
|
|
usb2_com_close(tp); /* close, if any */
|
|
|
|
tty_rel_gone(tp);
|
|
|
|
mtx_lock(sc->sc_mtx);
|
|
/* Wait for the callback after the TTY is torn down */
|
|
while (sc->sc_ttyfreed == 0)
|
|
usb2_cv_wait(&sc->sc_cv, sc->sc_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_mtx);
|
|
}
|
|
usb2_cv_destroy(&sc->sc_cv);
|
|
}
|
|
|
|
static void
|
|
usb2_com_queue_command(struct usb2_com_softc *sc,
|
|
usb2_proc_callback_t *fn, struct termios *pt,
|
|
struct usb2_proc_msg *t0, struct usb2_proc_msg *t1)
|
|
{
|
|
struct usb2_com_super_softc *ssc = sc->sc_super;
|
|
struct usb2_com_param_task *task;
|
|
|
|
mtx_assert(sc->sc_mtx, MA_OWNED);
|
|
|
|
if (usb2_proc_is_gone(&ssc->sc_tq)) {
|
|
DPRINTF("proc is gone\n");
|
|
return; /* nothing to do */
|
|
}
|
|
/*
|
|
* NOTE: The task cannot get executed before we drop the
|
|
* "sc_mtx" mutex. It is safe to update fields in the message
|
|
* structure after that the message got queued.
|
|
*/
|
|
task = (struct usb2_com_param_task *)
|
|
usb2_proc_msignal(&ssc->sc_tq, t0, t1);
|
|
|
|
/* Setup callback and softc pointers */
|
|
task->hdr.pm_callback = fn;
|
|
task->sc = sc;
|
|
|
|
/*
|
|
* Make a copy of the termios. This field is only present if
|
|
* the "pt" field is not NULL.
|
|
*/
|
|
if (pt != NULL)
|
|
task->termios_copy = *pt;
|
|
|
|
/*
|
|
* Closing the device should be synchronous.
|
|
*/
|
|
if (fn == usb2_com_cfg_close)
|
|
usb2_proc_mwait(&ssc->sc_tq, t0, t1);
|
|
|
|
}
|
|
|
|
static void
|
|
usb2_com_shutdown(struct usb2_com_softc *sc)
|
|
{
|
|
struct tty *tp = sc->sc_tty;
|
|
|
|
mtx_assert(sc->sc_mtx, MA_OWNED);
|
|
|
|
DPRINTF("\n");
|
|
|
|
/*
|
|
* Hang up if necessary:
|
|
*/
|
|
if (tp->t_termios.c_cflag & HUPCL) {
|
|
usb2_com_modem(tp, 0, SER_DTR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return values:
|
|
* 0: normal
|
|
* else: taskqueue is draining or 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_tq));
|
|
}
|
|
|
|
static void
|
|
usb2_com_cfg_start_transfers(struct usb2_proc_msg *_task)
|
|
{
|
|
struct usb2_com_cfg_task *task =
|
|
(struct usb2_com_cfg_task *)_task;
|
|
struct usb2_com_softc *sc = task->sc;
|
|
|
|
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;
|
|
}
|
|
/*
|
|
* Make sure that data transfers are started in both
|
|
* directions:
|
|
*/
|
|
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_cfg_open(struct usb2_proc_msg *_task)
|
|
{
|
|
struct usb2_com_cfg_task *task =
|
|
(struct usb2_com_cfg_task *)_task;
|
|
struct usb2_com_softc *sc = task->sc;
|
|
|
|
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_pause_mtx(sc->sc_mtx, hz / 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
usb2_com_open(struct tty *tp)
|
|
{
|
|
struct usb2_com_softc *sc = tty_softc(tp);
|
|
int error;
|
|
|
|
mtx_assert(sc->sc_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;
|
|
|
|
/* reset programmed line state */
|
|
sc->sc_pls_curr = 0;
|
|
sc->sc_pls_set = 0;
|
|
sc->sc_pls_clr = 0;
|
|
|
|
usb2_com_queue_command(sc, usb2_com_cfg_open, NULL,
|
|
&sc->sc_open_task[0].hdr,
|
|
&sc->sc_open_task[1].hdr);
|
|
|
|
/* Queue transfer enable command last */
|
|
usb2_com_queue_command(sc, usb2_com_cfg_start_transfers, NULL,
|
|
&sc->sc_start_task[0].hdr,
|
|
&sc->sc_start_task[1].hdr);
|
|
|
|
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 *_task)
|
|
{
|
|
struct usb2_com_cfg_task *task =
|
|
(struct usb2_com_cfg_task *)_task;
|
|
struct usb2_com_softc *sc = task->sc;
|
|
|
|
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_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);
|
|
|
|
usb2_com_queue_command(sc, usb2_com_cfg_close, NULL,
|
|
&sc->sc_close_task[0].hdr,
|
|
&sc->sc_close_task[1].hdr);
|
|
|
|
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_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_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_line_state(struct usb2_proc_msg *_task)
|
|
{
|
|
struct usb2_com_cfg_task *task =
|
|
(struct usb2_com_cfg_task *)_task;
|
|
struct usb2_com_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;
|
|
/* compute callback mask */
|
|
if (sc->sc_callback->usb2_com_cfg_set_dtr)
|
|
mask |= UCOM_LS_DTR;
|
|
if (sc->sc_callback->usb2_com_cfg_set_rts)
|
|
mask |= UCOM_LS_RTS;
|
|
if (sc->sc_callback->usb2_com_cfg_set_break)
|
|
mask |= UCOM_LS_BREAK;
|
|
|
|
/* compute the bits we are to program */
|
|
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;
|
|
|
|
/* reset programmed line state */
|
|
sc->sc_pls_curr = 0;
|
|
sc->sc_pls_set = 0;
|
|
sc->sc_pls_clr = 0;
|
|
|
|
/* ensure that we don't loose any levels */
|
|
if (notch_bits & UCOM_LS_DTR)
|
|
sc->sc_callback->usb2_com_cfg_set_dtr(sc,
|
|
(prev_value & UCOM_LS_DTR) ? 1 : 0);
|
|
if (notch_bits & UCOM_LS_RTS)
|
|
sc->sc_callback->usb2_com_cfg_set_rts(sc,
|
|
(prev_value & UCOM_LS_RTS) ? 1 : 0);
|
|
if (notch_bits & UCOM_LS_BREAK)
|
|
sc->sc_callback->usb2_com_cfg_set_break(sc,
|
|
(prev_value & UCOM_LS_BREAK) ? 1 : 0);
|
|
|
|
/* set last value */
|
|
if (any_bits & UCOM_LS_DTR)
|
|
sc->sc_callback->usb2_com_cfg_set_dtr(sc,
|
|
(last_value & UCOM_LS_DTR) ? 1 : 0);
|
|
if (any_bits & UCOM_LS_RTS)
|
|
sc->sc_callback->usb2_com_cfg_set_rts(sc,
|
|
(last_value & UCOM_LS_RTS) ? 1 : 0);
|
|
if (any_bits & UCOM_LS_BREAK)
|
|
sc->sc_callback->usb2_com_cfg_set_break(sc,
|
|
(last_value & UCOM_LS_BREAK) ? 1 : 0);
|
|
}
|
|
|
|
static void
|
|
usb2_com_line_state(struct usb2_com_softc *sc,
|
|
uint8_t set_bits, uint8_t clear_bits)
|
|
{
|
|
mtx_assert(sc->sc_mtx, MA_OWNED);
|
|
|
|
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
|
|
return;
|
|
}
|
|
|
|
DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
|
|
|
|
/* update current programmed line state */
|
|
sc->sc_pls_curr |= set_bits;
|
|
sc->sc_pls_curr &= ~clear_bits;
|
|
sc->sc_pls_set |= set_bits;
|
|
sc->sc_pls_clr |= clear_bits;
|
|
|
|
/* defer driver programming */
|
|
usb2_com_queue_command(sc, usb2_com_cfg_line_state, NULL,
|
|
&sc->sc_line_state_task[0].hdr,
|
|
&sc->sc_line_state_task[1].hdr);
|
|
}
|
|
|
|
static void
|
|
usb2_com_break(struct usb2_com_softc *sc, uint8_t onoff)
|
|
{
|
|
DPRINTF("onoff = %d\n", onoff);
|
|
|
|
if (onoff)
|
|
usb2_com_line_state(sc, UCOM_LS_BREAK, 0);
|
|
else
|
|
usb2_com_line_state(sc, 0, UCOM_LS_BREAK);
|
|
}
|
|
|
|
static void
|
|
usb2_com_dtr(struct usb2_com_softc *sc, uint8_t onoff)
|
|
{
|
|
DPRINTF("onoff = %d\n", onoff);
|
|
|
|
if (onoff)
|
|
usb2_com_line_state(sc, UCOM_LS_DTR, 0);
|
|
else
|
|
usb2_com_line_state(sc, 0, UCOM_LS_DTR);
|
|
}
|
|
|
|
static void
|
|
usb2_com_rts(struct usb2_com_softc *sc, uint8_t onoff)
|
|
{
|
|
DPRINTF("onoff = %d\n", onoff);
|
|
|
|
if (onoff)
|
|
usb2_com_line_state(sc, UCOM_LS_RTS, 0);
|
|
else
|
|
usb2_com_line_state(sc, 0, UCOM_LS_RTS);
|
|
}
|
|
|
|
static void
|
|
usb2_com_cfg_status_change(struct usb2_proc_msg *_task)
|
|
{
|
|
struct usb2_com_cfg_task *task =
|
|
(struct usb2_com_cfg_task *)_task;
|
|
struct usb2_com_softc *sc = task->sc;
|
|
struct tty *tp;
|
|
uint8_t new_msr;
|
|
uint8_t new_lsr;
|
|
uint8_t onoff;
|
|
|
|
tp = sc->sc_tty;
|
|
|
|
mtx_assert(sc->sc_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_mtx, MA_OWNED);
|
|
|
|
if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
|
|
return;
|
|
}
|
|
DPRINTF("\n");
|
|
|
|
usb2_com_queue_command(sc, usb2_com_cfg_status_change, NULL,
|
|
&sc->sc_status_task[0].hdr,
|
|
&sc->sc_status_task[1].hdr);
|
|
}
|
|
|
|
static void
|
|
usb2_com_cfg_param(struct usb2_proc_msg *_task)
|
|
{
|
|
struct usb2_com_param_task *task =
|
|
(struct usb2_com_param_task *)_task;
|
|
struct usb2_com_softc *sc = task->sc;
|
|
|
|
if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
|
|
return;
|
|
}
|
|
if (sc->sc_callback->usb2_com_cfg_param == NULL) {
|
|
return;
|
|
}
|
|
|
|
(sc->sc_callback->usb2_com_cfg_param) (sc, &task->termios_copy);
|
|
|
|
/* wait a little */
|
|
usb2_pause_mtx(sc->sc_mtx, 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_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;
|
|
}
|
|
}
|
|
|
|
/* Disable transfers */
|
|
sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
|
|
|
|
/* Queue baud rate programming command first */
|
|
usb2_com_queue_command(sc, usb2_com_cfg_param, t,
|
|
&sc->sc_param_task[0].hdr,
|
|
&sc->sc_param_task[1].hdr);
|
|
|
|
/* Queue transfer enable command last */
|
|
usb2_com_queue_command(sc, usb2_com_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;
|
|
usb2_com_modem(tp, SER_RTS, 0);
|
|
}
|
|
done:
|
|
if (error) {
|
|
if (opened) {
|
|
usb2_com_close(tp);
|
|
}
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
usb2_com_outwakeup(struct tty *tp)
|
|
{
|
|
struct usb2_com_softc *sc = tty_softc(tp);
|
|
|
|
mtx_assert(sc->sc_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_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_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_mtx);
|
|
sc->sc_ttyfreed = 1;
|
|
usb2_cv_signal(&sc->sc_cv);
|
|
mtx_unlock(sc->sc_mtx);
|
|
}
|