mirror of https://gitee.com/openkylin/linux.git
581 lines
14 KiB
C
581 lines
14 KiB
C
/*********************************************************************
|
|
*
|
|
* Filename: irtty-sir.c
|
|
* Version: 2.0
|
|
* Description: IrDA line discipline implementation
|
|
* Status: Experimental.
|
|
* Author: Dag Brattli <dagb@cs.uit.no>
|
|
* Created at: Tue Dec 9 21:18:38 1997
|
|
* Modified at: Sun Oct 27 22:13:30 2002
|
|
* Modified by: Martin Diehl <mad@mdiehl.de>
|
|
* Sources: slip.c by Laurence Culhane, <loz@holmes.demon.co.uk>
|
|
* Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
|
|
*
|
|
* Copyright (c) 1998-2000 Dag Brattli,
|
|
* Copyright (c) 2002 Martin Diehl,
|
|
* All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* Neither Dag Brattli nor University of Tromsø admit liability nor
|
|
* provide warranty for any of this software. This material is
|
|
* provided "AS-IS" and at no charge.
|
|
*
|
|
********************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/init.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#include <net/irda/irda.h>
|
|
#include <net/irda/irda_device.h>
|
|
|
|
#include "sir-dev.h"
|
|
#include "irtty-sir.h"
|
|
|
|
static int qos_mtt_bits = 0x03; /* 5 ms or more */
|
|
|
|
module_param(qos_mtt_bits, int, 0);
|
|
MODULE_PARM_DESC(qos_mtt_bits, "Minimum Turn Time");
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
/* device configuration callbacks always invoked with irda-thread context */
|
|
|
|
/* find out, how many chars we have in buffers below us
|
|
* this is allowed to lie, i.e. return less chars than we
|
|
* actually have. The returned value is used to determine
|
|
* how long the irdathread should wait before doing the
|
|
* real blocking wait_until_sent()
|
|
*/
|
|
|
|
static int irtty_chars_in_buffer(struct sir_dev *dev)
|
|
{
|
|
struct sirtty_cb *priv = dev->priv;
|
|
|
|
IRDA_ASSERT(priv != NULL, return -1;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return -1;);
|
|
|
|
return tty_chars_in_buffer(priv->tty);
|
|
}
|
|
|
|
/* Wait (sleep) until underlaying hardware finished transmission
|
|
* i.e. hardware buffers are drained
|
|
* this must block and not return before all characters are really sent
|
|
*
|
|
* If the tty sits on top of a 16550A-like uart, there are typically
|
|
* up to 16 bytes in the fifo - f.e. 9600 bps 8N1 needs 16.7 msec
|
|
*
|
|
* With usbserial the uart-fifo is basically replaced by the converter's
|
|
* outgoing endpoint buffer, which can usually hold 64 bytes (at least).
|
|
* With pl2303 it appears we are safe with 60msec here.
|
|
*
|
|
* I really wish all serial drivers would provide
|
|
* correct implementation of wait_until_sent()
|
|
*/
|
|
|
|
#define USBSERIAL_TX_DONE_DELAY 60
|
|
|
|
static void irtty_wait_until_sent(struct sir_dev *dev)
|
|
{
|
|
struct sirtty_cb *priv = dev->priv;
|
|
struct tty_struct *tty;
|
|
|
|
IRDA_ASSERT(priv != NULL, return;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return;);
|
|
|
|
tty = priv->tty;
|
|
if (tty->ops->wait_until_sent) {
|
|
tty->ops->wait_until_sent(tty, msecs_to_jiffies(100));
|
|
}
|
|
else {
|
|
msleep(USBSERIAL_TX_DONE_DELAY);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function irtty_change_speed (dev, speed)
|
|
*
|
|
* Change the speed of the serial port.
|
|
*
|
|
* This may sleep in set_termios (usbserial driver f.e.) and must
|
|
* not be called from interrupt/timer/tasklet therefore.
|
|
* All such invocations are deferred to kIrDAd now so we can sleep there.
|
|
*/
|
|
|
|
static int irtty_change_speed(struct sir_dev *dev, unsigned speed)
|
|
{
|
|
struct sirtty_cb *priv = dev->priv;
|
|
struct tty_struct *tty;
|
|
struct ktermios old_termios;
|
|
int cflag;
|
|
|
|
IRDA_ASSERT(priv != NULL, return -1;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return -1;);
|
|
|
|
tty = priv->tty;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
old_termios = *(tty->termios);
|
|
cflag = tty->termios->c_cflag;
|
|
tty_encode_baud_rate(tty, speed, speed);
|
|
if (tty->ops->set_termios)
|
|
tty->ops->set_termios(tty, &old_termios);
|
|
priv->io.speed = speed;
|
|
mutex_unlock(&tty->termios_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function irtty_set_dtr_rts (dev, dtr, rts)
|
|
*
|
|
* This function can be used by dongles etc. to set or reset the status
|
|
* of the dtr and rts lines
|
|
*/
|
|
|
|
static int irtty_set_dtr_rts(struct sir_dev *dev, int dtr, int rts)
|
|
{
|
|
struct sirtty_cb *priv = dev->priv;
|
|
int set = 0;
|
|
int clear = 0;
|
|
|
|
IRDA_ASSERT(priv != NULL, return -1;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return -1;);
|
|
|
|
if (rts)
|
|
set |= TIOCM_RTS;
|
|
else
|
|
clear |= TIOCM_RTS;
|
|
if (dtr)
|
|
set |= TIOCM_DTR;
|
|
else
|
|
clear |= TIOCM_DTR;
|
|
|
|
/*
|
|
* We can't use ioctl() because it expects a non-null file structure,
|
|
* and we don't have that here.
|
|
* This function is not yet defined for all tty driver, so
|
|
* let's be careful... Jean II
|
|
*/
|
|
IRDA_ASSERT(priv->tty->ops->tiocmset != NULL, return -1;);
|
|
priv->tty->ops->tiocmset(priv->tty, NULL, set, clear);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
/* called from sir_dev when there is more data to send
|
|
* context is either netdev->hard_xmit or some transmit-completion bh
|
|
* i.e. we are under spinlock here and must not sleep.
|
|
*/
|
|
|
|
static int irtty_do_write(struct sir_dev *dev, const unsigned char *ptr, size_t len)
|
|
{
|
|
struct sirtty_cb *priv = dev->priv;
|
|
struct tty_struct *tty;
|
|
int writelen;
|
|
|
|
IRDA_ASSERT(priv != NULL, return -1;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return -1;);
|
|
|
|
tty = priv->tty;
|
|
if (!tty->ops->write)
|
|
return 0;
|
|
tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
|
|
writelen = tty_write_room(tty);
|
|
if (writelen > len)
|
|
writelen = len;
|
|
return tty->ops->write(tty, ptr, writelen);
|
|
}
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
/* irda line discipline callbacks */
|
|
|
|
/*
|
|
* Function irtty_receive_buf( tty, cp, count)
|
|
*
|
|
* Handle the 'receiver data ready' interrupt. This function is called
|
|
* by the 'tty_io' module in the kernel when a block of IrDA data has
|
|
* been received, which can now be decapsulated and delivered for
|
|
* further processing
|
|
*
|
|
* calling context depends on underlying driver and tty->low_latency!
|
|
* for example (low_latency: 1 / 0):
|
|
* serial.c: uart-interrupt / softint
|
|
* usbserial: urb-complete-interrupt / softint
|
|
*/
|
|
|
|
static void irtty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
|
|
char *fp, int count)
|
|
{
|
|
struct sir_dev *dev;
|
|
struct sirtty_cb *priv = tty->disc_data;
|
|
int i;
|
|
|
|
IRDA_ASSERT(priv != NULL, return;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return;);
|
|
|
|
if (unlikely(count==0)) /* yes, this happens */
|
|
return;
|
|
|
|
dev = priv->dev;
|
|
if (!dev) {
|
|
IRDA_WARNING("%s(), not ready yet!\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
/*
|
|
* Characters received with a parity error, etc?
|
|
*/
|
|
if (fp && *fp++) {
|
|
IRDA_DEBUG(0, "Framing or parity error!\n");
|
|
sirdev_receive(dev, NULL, 0); /* notify sir_dev (updating stats) */
|
|
return;
|
|
}
|
|
}
|
|
|
|
sirdev_receive(dev, cp, count);
|
|
}
|
|
|
|
/*
|
|
* Function irtty_write_wakeup (tty)
|
|
*
|
|
* Called by the driver when there's room for more data. If we have
|
|
* more packets to send, we send them here.
|
|
*
|
|
*/
|
|
static void irtty_write_wakeup(struct tty_struct *tty)
|
|
{
|
|
struct sirtty_cb *priv = tty->disc_data;
|
|
|
|
IRDA_ASSERT(priv != NULL, return;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return;);
|
|
|
|
tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
|
|
|
|
if (priv->dev)
|
|
sirdev_write_complete(priv->dev);
|
|
}
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
/*
|
|
* Function irtty_stop_receiver (tty, stop)
|
|
*
|
|
*/
|
|
|
|
static inline void irtty_stop_receiver(struct tty_struct *tty, int stop)
|
|
{
|
|
struct ktermios old_termios;
|
|
int cflag;
|
|
|
|
mutex_lock(&tty->termios_mutex);
|
|
old_termios = *(tty->termios);
|
|
cflag = tty->termios->c_cflag;
|
|
|
|
if (stop)
|
|
cflag &= ~CREAD;
|
|
else
|
|
cflag |= CREAD;
|
|
|
|
tty->termios->c_cflag = cflag;
|
|
if (tty->ops->set_termios)
|
|
tty->ops->set_termios(tty, &old_termios);
|
|
mutex_unlock(&tty->termios_mutex);
|
|
}
|
|
|
|
/*****************************************************************/
|
|
|
|
/* serialize ldisc open/close with sir_dev */
|
|
static DEFINE_MUTEX(irtty_mutex);
|
|
|
|
/* notifier from sir_dev when irda% device gets opened (ifup) */
|
|
|
|
static int irtty_start_dev(struct sir_dev *dev)
|
|
{
|
|
struct sirtty_cb *priv;
|
|
struct tty_struct *tty;
|
|
|
|
/* serialize with ldisc open/close */
|
|
mutex_lock(&irtty_mutex);
|
|
|
|
priv = dev->priv;
|
|
if (unlikely(!priv || priv->magic!=IRTTY_MAGIC)) {
|
|
mutex_unlock(&irtty_mutex);
|
|
return -ESTALE;
|
|
}
|
|
|
|
tty = priv->tty;
|
|
|
|
if (tty->ops->start)
|
|
tty->ops->start(tty);
|
|
/* Make sure we can receive more data */
|
|
irtty_stop_receiver(tty, FALSE);
|
|
|
|
mutex_unlock(&irtty_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* notifier from sir_dev when irda% device gets closed (ifdown) */
|
|
|
|
static int irtty_stop_dev(struct sir_dev *dev)
|
|
{
|
|
struct sirtty_cb *priv;
|
|
struct tty_struct *tty;
|
|
|
|
/* serialize with ldisc open/close */
|
|
mutex_lock(&irtty_mutex);
|
|
|
|
priv = dev->priv;
|
|
if (unlikely(!priv || priv->magic!=IRTTY_MAGIC)) {
|
|
mutex_unlock(&irtty_mutex);
|
|
return -ESTALE;
|
|
}
|
|
|
|
tty = priv->tty;
|
|
|
|
/* Make sure we don't receive more data */
|
|
irtty_stop_receiver(tty, TRUE);
|
|
if (tty->ops->stop)
|
|
tty->ops->stop(tty);
|
|
|
|
mutex_unlock(&irtty_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
static struct sir_driver sir_tty_drv = {
|
|
.owner = THIS_MODULE,
|
|
.driver_name = "sir_tty",
|
|
.start_dev = irtty_start_dev,
|
|
.stop_dev = irtty_stop_dev,
|
|
.do_write = irtty_do_write,
|
|
.chars_in_buffer = irtty_chars_in_buffer,
|
|
.wait_until_sent = irtty_wait_until_sent,
|
|
.set_speed = irtty_change_speed,
|
|
.set_dtr_rts = irtty_set_dtr_rts,
|
|
};
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
/*
|
|
* Function irtty_ioctl (tty, file, cmd, arg)
|
|
*
|
|
* The Swiss army knife of system calls :-)
|
|
*
|
|
*/
|
|
static int irtty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct irtty_info { char name[6]; } info;
|
|
struct sir_dev *dev;
|
|
struct sirtty_cb *priv = tty->disc_data;
|
|
int err = 0;
|
|
|
|
IRDA_ASSERT(priv != NULL, return -ENODEV;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return -EBADR;);
|
|
|
|
IRDA_DEBUG(3, "%s(cmd=0x%X)\n", __FUNCTION__, cmd);
|
|
|
|
dev = priv->dev;
|
|
IRDA_ASSERT(dev != NULL, return -1;);
|
|
|
|
switch (cmd) {
|
|
case IRTTY_IOCTDONGLE:
|
|
/* this call blocks for completion */
|
|
err = sirdev_set_dongle(dev, (IRDA_DONGLE) arg);
|
|
break;
|
|
|
|
case IRTTY_IOCGET:
|
|
IRDA_ASSERT(dev->netdev != NULL, return -1;);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
strncpy(info.name, dev->netdev->name, sizeof(info.name)-1);
|
|
|
|
if (copy_to_user((void __user *)arg, &info, sizeof(info)))
|
|
err = -EFAULT;
|
|
break;
|
|
default:
|
|
err = tty_mode_ioctl(tty, file, cmd, arg);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function irtty_open(tty)
|
|
*
|
|
* This function is called by the TTY module when the IrDA line
|
|
* discipline is called for. Because we are sure the tty line exists,
|
|
* we only have to link it to a free IrDA channel.
|
|
*/
|
|
static int irtty_open(struct tty_struct *tty)
|
|
{
|
|
struct sir_dev *dev;
|
|
struct sirtty_cb *priv;
|
|
int ret = 0;
|
|
|
|
/* Module stuff handled via irda_ldisc.owner - Jean II */
|
|
|
|
/* First make sure we're not already connected. */
|
|
if (tty->disc_data != NULL) {
|
|
priv = tty->disc_data;
|
|
if (priv && priv->magic == IRTTY_MAGIC) {
|
|
ret = -EEXIST;
|
|
goto out;
|
|
}
|
|
tty->disc_data = NULL; /* ### */
|
|
}
|
|
|
|
/* stop the underlying driver */
|
|
irtty_stop_receiver(tty, TRUE);
|
|
if (tty->ops->stop)
|
|
tty->ops->stop(tty);
|
|
|
|
tty_driver_flush_buffer(tty);
|
|
|
|
/* apply mtt override */
|
|
sir_tty_drv.qos_mtt_bits = qos_mtt_bits;
|
|
|
|
/* get a sir device instance for this driver */
|
|
dev = sirdev_get_instance(&sir_tty_drv, tty->name);
|
|
if (!dev) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
/* allocate private device info block */
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
goto out_put;
|
|
|
|
priv->magic = IRTTY_MAGIC;
|
|
priv->tty = tty;
|
|
priv->dev = dev;
|
|
|
|
/* serialize with start_dev - in case we were racing with ifup */
|
|
mutex_lock(&irtty_mutex);
|
|
|
|
dev->priv = priv;
|
|
tty->disc_data = priv;
|
|
tty->receive_room = 65536;
|
|
|
|
mutex_unlock(&irtty_mutex);
|
|
|
|
IRDA_DEBUG(0, "%s - %s: irda line discipline opened\n", __FUNCTION__, tty->name);
|
|
|
|
return 0;
|
|
|
|
out_put:
|
|
sirdev_put_instance(dev);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Function irtty_close (tty)
|
|
*
|
|
* Close down a IrDA channel. This means flushing out any pending queues,
|
|
* and then restoring the TTY line discipline to what it was before it got
|
|
* hooked to IrDA (which usually is TTY again).
|
|
*/
|
|
static void irtty_close(struct tty_struct *tty)
|
|
{
|
|
struct sirtty_cb *priv = tty->disc_data;
|
|
|
|
IRDA_ASSERT(priv != NULL, return;);
|
|
IRDA_ASSERT(priv->magic == IRTTY_MAGIC, return;);
|
|
|
|
/* Hm, with a dongle attached the dongle driver wants
|
|
* to close the dongle - which requires the use of
|
|
* some tty write and/or termios or ioctl operations.
|
|
* Are we allowed to call those when already requested
|
|
* to shutdown the ldisc?
|
|
* If not, we should somehow mark the dev being staled.
|
|
* Question remains, how to close the dongle in this case...
|
|
* For now let's assume we are granted to issue tty driver calls
|
|
* until we return here from the ldisc close. I'm just wondering
|
|
* how this behaves with hotpluggable serial hardware like
|
|
* rs232-pcmcia card or usb-serial...
|
|
*
|
|
* priv->tty = NULL?;
|
|
*/
|
|
|
|
/* we are dead now */
|
|
tty->disc_data = NULL;
|
|
|
|
sirdev_put_instance(priv->dev);
|
|
|
|
/* Stop tty */
|
|
irtty_stop_receiver(tty, TRUE);
|
|
tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
|
|
if (tty->ops->stop)
|
|
tty->ops->stop(tty);
|
|
|
|
kfree(priv);
|
|
|
|
IRDA_DEBUG(0, "%s - %s: irda line discipline closed\n", __FUNCTION__, tty->name);
|
|
}
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
static struct tty_ldisc irda_ldisc = {
|
|
.magic = TTY_LDISC_MAGIC,
|
|
.name = "irda",
|
|
.flags = 0,
|
|
.open = irtty_open,
|
|
.close = irtty_close,
|
|
.read = NULL,
|
|
.write = NULL,
|
|
.ioctl = irtty_ioctl,
|
|
.poll = NULL,
|
|
.receive_buf = irtty_receive_buf,
|
|
.write_wakeup = irtty_write_wakeup,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
/* ------------------------------------------------------- */
|
|
|
|
static int __init irtty_sir_init(void)
|
|
{
|
|
int err;
|
|
|
|
if ((err = tty_register_ldisc(N_IRDA, &irda_ldisc)) != 0)
|
|
IRDA_ERROR("IrDA: can't register line discipline (err = %d)\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
static void __exit irtty_sir_cleanup(void)
|
|
{
|
|
int err;
|
|
|
|
if ((err = tty_unregister_ldisc(N_IRDA))) {
|
|
IRDA_ERROR("%s(), can't unregister line discipline (err = %d)\n",
|
|
__FUNCTION__, err);
|
|
}
|
|
}
|
|
|
|
module_init(irtty_sir_init);
|
|
module_exit(irtty_sir_cleanup);
|
|
|
|
MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>");
|
|
MODULE_DESCRIPTION("IrDA TTY device driver");
|
|
MODULE_ALIAS_LDISC(N_IRDA);
|
|
MODULE_LICENSE("GPL");
|
|
|