linux/net/irda/ircomm/ircomm_tty.c
Amit Virdi cbfd1526f6 net/irda/ircomm_tty.c: Use flip buffers to deliver data
use tty_insert_flip_string and tty_flip_buffer_push to deliver incoming data
packets from the IrDA device instead of delivering the packets directly to the
line discipline. Following later approach resulted in warning "Sleeping function
called from invalid context".

Signed-off-by: Amit Virdi <amit.virdi@st.com>
Acked-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-05-12 18:02:51 -04:00

1412 lines
38 KiB
C

/*********************************************************************
*
* Filename: ircomm_tty.c
* Version: 1.0
* Description: IrCOMM serial TTY driver
* Status: Experimental.
* Author: Dag Brattli <dagb@cs.uit.no>
* Created at: Sun Jun 6 21:00:56 1999
* Modified at: Wed Feb 23 00:09:02 2000
* Modified by: Dag Brattli <dagb@cs.uit.no>
* Sources: serial.c and previous IrCOMM work by Takahide Higuchi
*
* Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
* Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
********************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/termios.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/interrupt.h>
#include <linux/device.h> /* for MODULE_ALIAS_CHARDEV_MAJOR */
#include <asm/uaccess.h>
#include <net/irda/irda.h>
#include <net/irda/irmod.h>
#include <net/irda/ircomm_core.h>
#include <net/irda/ircomm_param.h>
#include <net/irda/ircomm_tty_attach.h>
#include <net/irda/ircomm_tty.h>
static int ircomm_tty_open(struct tty_struct *tty, struct file *filp);
static void ircomm_tty_close(struct tty_struct * tty, struct file *filp);
static int ircomm_tty_write(struct tty_struct * tty,
const unsigned char *buf, int count);
static int ircomm_tty_write_room(struct tty_struct *tty);
static void ircomm_tty_throttle(struct tty_struct *tty);
static void ircomm_tty_unthrottle(struct tty_struct *tty);
static int ircomm_tty_chars_in_buffer(struct tty_struct *tty);
static void ircomm_tty_flush_buffer(struct tty_struct *tty);
static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch);
static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout);
static void ircomm_tty_hangup(struct tty_struct *tty);
static void ircomm_tty_do_softint(struct work_struct *work);
static void ircomm_tty_shutdown(struct ircomm_tty_cb *self);
static void ircomm_tty_stop(struct tty_struct *tty);
static int ircomm_tty_data_indication(void *instance, void *sap,
struct sk_buff *skb);
static int ircomm_tty_control_indication(void *instance, void *sap,
struct sk_buff *skb);
static void ircomm_tty_flow_indication(void *instance, void *sap,
LOCAL_FLOW cmd);
#ifdef CONFIG_PROC_FS
static const struct file_operations ircomm_tty_proc_fops;
#endif /* CONFIG_PROC_FS */
static struct tty_driver *driver;
static hashbin_t *ircomm_tty = NULL;
static const struct tty_operations ops = {
.open = ircomm_tty_open,
.close = ircomm_tty_close,
.write = ircomm_tty_write,
.write_room = ircomm_tty_write_room,
.chars_in_buffer = ircomm_tty_chars_in_buffer,
.flush_buffer = ircomm_tty_flush_buffer,
.ioctl = ircomm_tty_ioctl, /* ircomm_tty_ioctl.c */
.tiocmget = ircomm_tty_tiocmget, /* ircomm_tty_ioctl.c */
.tiocmset = ircomm_tty_tiocmset, /* ircomm_tty_ioctl.c */
.throttle = ircomm_tty_throttle,
.unthrottle = ircomm_tty_unthrottle,
.send_xchar = ircomm_tty_send_xchar,
.set_termios = ircomm_tty_set_termios,
.stop = ircomm_tty_stop,
.start = ircomm_tty_start,
.hangup = ircomm_tty_hangup,
.wait_until_sent = ircomm_tty_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &ircomm_tty_proc_fops,
#endif /* CONFIG_PROC_FS */
};
/*
* Function ircomm_tty_init()
*
* Init IrCOMM TTY layer/driver
*
*/
static int __init ircomm_tty_init(void)
{
driver = alloc_tty_driver(IRCOMM_TTY_PORTS);
if (!driver)
return -ENOMEM;
ircomm_tty = hashbin_new(HB_LOCK);
if (ircomm_tty == NULL) {
IRDA_ERROR("%s(), can't allocate hashbin!\n", __func__);
put_tty_driver(driver);
return -ENOMEM;
}
driver->owner = THIS_MODULE;
driver->driver_name = "ircomm";
driver->name = "ircomm";
driver->major = IRCOMM_TTY_MAJOR;
driver->minor_start = IRCOMM_TTY_MINOR;
driver->type = TTY_DRIVER_TYPE_SERIAL;
driver->subtype = SERIAL_TYPE_NORMAL;
driver->init_termios = tty_std_termios;
driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
driver->flags = TTY_DRIVER_REAL_RAW;
tty_set_operations(driver, &ops);
if (tty_register_driver(driver)) {
IRDA_ERROR("%s(): Couldn't register serial driver\n",
__func__);
put_tty_driver(driver);
return -1;
}
return 0;
}
static void __exit __ircomm_tty_cleanup(struct ircomm_tty_cb *self)
{
IRDA_DEBUG(0, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
ircomm_tty_shutdown(self);
self->magic = 0;
kfree(self);
}
/*
* Function ircomm_tty_cleanup ()
*
* Remove IrCOMM TTY layer/driver
*
*/
static void __exit ircomm_tty_cleanup(void)
{
int ret;
IRDA_DEBUG(4, "%s()\n", __func__ );
ret = tty_unregister_driver(driver);
if (ret) {
IRDA_ERROR("%s(), failed to unregister driver\n",
__func__);
return;
}
hashbin_delete(ircomm_tty, (FREE_FUNC) __ircomm_tty_cleanup);
put_tty_driver(driver);
}
/*
* Function ircomm_startup (self)
*
*
*
*/
static int ircomm_tty_startup(struct ircomm_tty_cb *self)
{
notify_t notify;
int ret = -ENODEV;
IRDA_DEBUG(2, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
/* Check if already open */
if (test_and_set_bit(ASYNC_B_INITIALIZED, &self->flags)) {
IRDA_DEBUG(2, "%s(), already open so break out!\n", __func__ );
return 0;
}
/* Register with IrCOMM */
irda_notify_init(&notify);
/* These callbacks we must handle ourselves */
notify.data_indication = ircomm_tty_data_indication;
notify.udata_indication = ircomm_tty_control_indication;
notify.flow_indication = ircomm_tty_flow_indication;
/* Use the ircomm_tty interface for these ones */
notify.disconnect_indication = ircomm_tty_disconnect_indication;
notify.connect_confirm = ircomm_tty_connect_confirm;
notify.connect_indication = ircomm_tty_connect_indication;
strlcpy(notify.name, "ircomm_tty", sizeof(notify.name));
notify.instance = self;
if (!self->ircomm) {
self->ircomm = ircomm_open(&notify, self->service_type,
self->line);
}
if (!self->ircomm)
goto err;
self->slsap_sel = self->ircomm->slsap_sel;
/* Connect IrCOMM link with remote device */
ret = ircomm_tty_attach_cable(self);
if (ret < 0) {
IRDA_ERROR("%s(), error attaching cable!\n", __func__);
goto err;
}
return 0;
err:
clear_bit(ASYNC_B_INITIALIZED, &self->flags);
return ret;
}
/*
* Function ircomm_block_til_ready (self, filp)
*
*
*
*/
static int ircomm_tty_block_til_ready(struct ircomm_tty_cb *self,
struct file *filp)
{
DECLARE_WAITQUEUE(wait, current);
int retval;
int do_clocal = 0, extra_count = 0;
unsigned long flags;
struct tty_struct *tty;
IRDA_DEBUG(2, "%s()\n", __func__ );
tty = self->tty;
/*
* If non-blocking mode is set, or the port is not enabled,
* then make the check up front and then exit.
*/
if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
/* nonblock mode is set or port is not enabled */
self->flags |= ASYNC_NORMAL_ACTIVE;
IRDA_DEBUG(1, "%s(), O_NONBLOCK requested!\n", __func__ );
return 0;
}
if (tty->termios->c_cflag & CLOCAL) {
IRDA_DEBUG(1, "%s(), doing CLOCAL!\n", __func__ );
do_clocal = 1;
}
/* Wait for carrier detect and the line to become
* free (i.e., not in use by the callout). While we are in
* this loop, self->open_count is dropped by one, so that
* mgsl_close() knows when to free things. We restore it upon
* exit, either normal or abnormal.
*/
retval = 0;
add_wait_queue(&self->open_wait, &wait);
IRDA_DEBUG(2, "%s(%d):block_til_ready before block on %s open_count=%d\n",
__FILE__,__LINE__, tty->driver->name, self->open_count );
/* As far as I can see, we protect open_count - Jean II */
spin_lock_irqsave(&self->spinlock, flags);
if (!tty_hung_up_p(filp)) {
extra_count = 1;
self->open_count--;
}
spin_unlock_irqrestore(&self->spinlock, flags);
self->blocked_open++;
while (1) {
if (tty->termios->c_cflag & CBAUD) {
/* Here, we use to lock those two guys, but
* as ircomm_param_request() does it itself,
* I don't see the point (and I see the deadlock).
* Jean II */
self->settings.dte |= IRCOMM_RTS + IRCOMM_DTR;
ircomm_param_request(self, IRCOMM_DTE, TRUE);
}
current->state = TASK_INTERRUPTIBLE;
if (tty_hung_up_p(filp) ||
!test_bit(ASYNC_B_INITIALIZED, &self->flags)) {
retval = (self->flags & ASYNC_HUP_NOTIFY) ?
-EAGAIN : -ERESTARTSYS;
break;
}
/*
* Check if link is ready now. Even if CLOCAL is
* specified, we cannot return before the IrCOMM link is
* ready
*/
if (!test_bit(ASYNC_B_CLOSING, &self->flags) &&
(do_clocal || (self->settings.dce & IRCOMM_CD)) &&
self->state == IRCOMM_TTY_READY)
{
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
IRDA_DEBUG(1, "%s(%d):block_til_ready blocking on %s open_count=%d\n",
__FILE__,__LINE__, tty->driver->name, self->open_count );
schedule();
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&self->open_wait, &wait);
if (extra_count) {
/* ++ is not atomic, so this should be protected - Jean II */
spin_lock_irqsave(&self->spinlock, flags);
self->open_count++;
spin_unlock_irqrestore(&self->spinlock, flags);
}
self->blocked_open--;
IRDA_DEBUG(1, "%s(%d):block_til_ready after blocking on %s open_count=%d\n",
__FILE__,__LINE__, tty->driver->name, self->open_count);
if (!retval)
self->flags |= ASYNC_NORMAL_ACTIVE;
return retval;
}
/*
* Function ircomm_tty_open (tty, filp)
*
* This routine is called when a particular tty device is opened. This
* routine is mandatory; if this routine is not filled in, the attempted
* open will fail with ENODEV.
*/
static int ircomm_tty_open(struct tty_struct *tty, struct file *filp)
{
struct ircomm_tty_cb *self;
unsigned int line;
unsigned long flags;
int ret;
IRDA_DEBUG(2, "%s()\n", __func__ );
line = tty->index;
if (line >= IRCOMM_TTY_PORTS)
return -ENODEV;
/* Check if instance already exists */
self = hashbin_lock_find(ircomm_tty, line, NULL);
if (!self) {
/* No, so make new instance */
self = kzalloc(sizeof(struct ircomm_tty_cb), GFP_KERNEL);
if (self == NULL) {
IRDA_ERROR("%s(), kmalloc failed!\n", __func__);
return -ENOMEM;
}
self->magic = IRCOMM_TTY_MAGIC;
self->flow = FLOW_STOP;
self->line = line;
INIT_WORK(&self->tqueue, ircomm_tty_do_softint);
self->max_header_size = IRCOMM_TTY_HDR_UNINITIALISED;
self->max_data_size = IRCOMM_TTY_DATA_UNINITIALISED;
self->close_delay = 5*HZ/10;
self->closing_wait = 30*HZ;
/* Init some important stuff */
init_timer(&self->watchdog_timer);
init_waitqueue_head(&self->open_wait);
init_waitqueue_head(&self->close_wait);
spin_lock_init(&self->spinlock);
/*
* Force TTY into raw mode by default which is usually what
* we want for IrCOMM and IrLPT. This way applications will
* not have to twiddle with printcap etc.
*
* Note this is completely usafe and doesn't work properly
*/
tty->termios->c_iflag = 0;
tty->termios->c_oflag = 0;
/* Insert into hash */
hashbin_insert(ircomm_tty, (irda_queue_t *) self, line, NULL);
}
/* ++ is not atomic, so this should be protected - Jean II */
spin_lock_irqsave(&self->spinlock, flags);
self->open_count++;
tty->driver_data = self;
self->tty = tty;
spin_unlock_irqrestore(&self->spinlock, flags);
IRDA_DEBUG(1, "%s(), %s%d, count = %d\n", __func__ , tty->driver->name,
self->line, self->open_count);
/* Not really used by us, but lets do it anyway */
self->tty->low_latency = (self->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
/*
* If the port is the middle of closing, bail out now
*/
if (tty_hung_up_p(filp) ||
test_bit(ASYNC_B_CLOSING, &self->flags)) {
/* Hm, why are we blocking on ASYNC_CLOSING if we
* do return -EAGAIN/-ERESTARTSYS below anyway?
* IMHO it's either not needed in the first place
* or for some reason we need to make sure the async
* closing has been finished - if so, wouldn't we
* probably better sleep uninterruptible?
*/
if (wait_event_interruptible(self->close_wait, !test_bit(ASYNC_B_CLOSING, &self->flags))) {
IRDA_WARNING("%s - got signal while blocking on ASYNC_CLOSING!\n",
__func__);
return -ERESTARTSYS;
}
#ifdef SERIAL_DO_RESTART
return (self->flags & ASYNC_HUP_NOTIFY) ?
-EAGAIN : -ERESTARTSYS;
#else
return -EAGAIN;
#endif
}
/* Check if this is a "normal" ircomm device, or an irlpt device */
if (line < 0x10) {
self->service_type = IRCOMM_3_WIRE | IRCOMM_9_WIRE;
self->settings.service_type = IRCOMM_9_WIRE; /* 9 wire as default */
/* Jan Kiszka -> add DSR/RI -> Conform to IrCOMM spec */
self->settings.dce = IRCOMM_CTS | IRCOMM_CD | IRCOMM_DSR | IRCOMM_RI; /* Default line settings */
IRDA_DEBUG(2, "%s(), IrCOMM device\n", __func__ );
} else {
IRDA_DEBUG(2, "%s(), IrLPT device\n", __func__ );
self->service_type = IRCOMM_3_WIRE_RAW;
self->settings.service_type = IRCOMM_3_WIRE_RAW; /* Default */
}
ret = ircomm_tty_startup(self);
if (ret)
return ret;
ret = ircomm_tty_block_til_ready(self, filp);
if (ret) {
IRDA_DEBUG(2,
"%s(), returning after block_til_ready with %d\n", __func__ ,
ret);
return ret;
}
return 0;
}
/*
* Function ircomm_tty_close (tty, filp)
*
* This routine is called when a particular tty device is closed.
*
*/
static void ircomm_tty_close(struct tty_struct *tty, struct file *filp)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long flags;
IRDA_DEBUG(0, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
spin_lock_irqsave(&self->spinlock, flags);
if (tty_hung_up_p(filp)) {
spin_unlock_irqrestore(&self->spinlock, flags);
IRDA_DEBUG(0, "%s(), returning 1\n", __func__ );
return;
}
if ((tty->count == 1) && (self->open_count != 1)) {
/*
* Uh, oh. tty->count is 1, which means that the tty
* structure will be freed. state->count should always
* be one in these conditions. If it's greater than
* one, we've got real problems, since it means the
* serial port won't be shutdown.
*/
IRDA_DEBUG(0, "%s(), bad serial port count; "
"tty->count is 1, state->count is %d\n", __func__ ,
self->open_count);
self->open_count = 1;
}
if (--self->open_count < 0) {
IRDA_ERROR("%s(), bad serial port count for ttys%d: %d\n",
__func__, self->line, self->open_count);
self->open_count = 0;
}
if (self->open_count) {
spin_unlock_irqrestore(&self->spinlock, flags);
IRDA_DEBUG(0, "%s(), open count > 0\n", __func__ );
return;
}
/* Hum... Should be test_and_set_bit ??? - Jean II */
set_bit(ASYNC_B_CLOSING, &self->flags);
/* We need to unlock here (we were unlocking at the end of this
* function), because tty_wait_until_sent() may schedule.
* I don't know if the rest should be protected somehow,
* so someone should check. - Jean II */
spin_unlock_irqrestore(&self->spinlock, flags);
/*
* Now we wait for the transmit buffer to clear; and we notify
* the line discipline to only process XON/XOFF characters.
*/
tty->closing = 1;
if (self->closing_wait != ASYNC_CLOSING_WAIT_NONE)
tty_wait_until_sent(tty, self->closing_wait);
ircomm_tty_shutdown(self);
tty_driver_flush_buffer(tty);
tty_ldisc_flush(tty);
tty->closing = 0;
self->tty = NULL;
if (self->blocked_open) {
if (self->close_delay)
schedule_timeout_interruptible(self->close_delay);
wake_up_interruptible(&self->open_wait);
}
self->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
wake_up_interruptible(&self->close_wait);
}
/*
* Function ircomm_tty_flush_buffer (tty)
*
*
*
*/
static void ircomm_tty_flush_buffer(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
/*
* Let do_softint() do this to avoid race condition with
* do_softint() ;-)
*/
schedule_work(&self->tqueue);
}
/*
* Function ircomm_tty_do_softint (work)
*
* We use this routine to give the write wakeup to the user at at a
* safe time (as fast as possible after write have completed). This
* can be compared to the Tx interrupt.
*/
static void ircomm_tty_do_softint(struct work_struct *work)
{
struct ircomm_tty_cb *self =
container_of(work, struct ircomm_tty_cb, tqueue);
struct tty_struct *tty;
unsigned long flags;
struct sk_buff *skb, *ctrl_skb;
IRDA_DEBUG(2, "%s()\n", __func__ );
if (!self || self->magic != IRCOMM_TTY_MAGIC)
return;
tty = self->tty;
if (!tty)
return;
/* Unlink control buffer */
spin_lock_irqsave(&self->spinlock, flags);
ctrl_skb = self->ctrl_skb;
self->ctrl_skb = NULL;
spin_unlock_irqrestore(&self->spinlock, flags);
/* Flush control buffer if any */
if(ctrl_skb) {
if(self->flow == FLOW_START)
ircomm_control_request(self->ircomm, ctrl_skb);
/* Drop reference count - see ircomm_ttp_data_request(). */
dev_kfree_skb(ctrl_skb);
}
if (tty->hw_stopped)
return;
/* Unlink transmit buffer */
spin_lock_irqsave(&self->spinlock, flags);
skb = self->tx_skb;
self->tx_skb = NULL;
spin_unlock_irqrestore(&self->spinlock, flags);
/* Flush transmit buffer if any */
if (skb) {
ircomm_tty_do_event(self, IRCOMM_TTY_DATA_REQUEST, skb, NULL);
/* Drop reference count - see ircomm_ttp_data_request(). */
dev_kfree_skb(skb);
}
/* Check if user (still) wants to be waken up */
tty_wakeup(tty);
}
/*
* Function ircomm_tty_write (tty, buf, count)
*
* This routine is called by the kernel to write a series of characters
* to the tty device. The characters may come from user space or kernel
* space. This routine will return the number of characters actually
* accepted for writing. This routine is mandatory.
*/
static int ircomm_tty_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long flags;
struct sk_buff *skb;
int tailroom = 0;
int len = 0;
int size;
IRDA_DEBUG(2, "%s(), count=%d, hw_stopped=%d\n", __func__ , count,
tty->hw_stopped);
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
/* We may receive packets from the TTY even before we have finished
* our setup. Not cool.
* The problem is that we don't know the final header and data size
* to create the proper skb, so any skb we would create would have
* bogus header and data size, so need care.
* We use a bogus header size to safely detect this condition.
* Another problem is that hw_stopped was set to 0 way before it
* should be, so we would drop this skb. It should now be fixed.
* One option is to not accept data until we are properly setup.
* But, I suspect that when it happens, the ppp line discipline
* just "drops" the data, which might screw up connect scripts.
* The second option is to create a "safe skb", with large header
* and small size (see ircomm_tty_open() for values).
* We just need to make sure that when the real values get filled,
* we don't mess up the original "safe skb" (see tx_data_size).
* Jean II */
if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED) {
IRDA_DEBUG(1, "%s() : not initialised\n", __func__);
#ifdef IRCOMM_NO_TX_BEFORE_INIT
/* We didn't consume anything, TTY will retry */
return 0;
#endif
}
if (count < 1)
return 0;
/* Protect our manipulation of self->tx_skb and related */
spin_lock_irqsave(&self->spinlock, flags);
/* Fetch current transmit buffer */
skb = self->tx_skb;
/*
* Send out all the data we get, possibly as multiple fragmented
* frames, but this will only happen if the data is larger than the
* max data size. The normal case however is just the opposite, and
* this function may be called multiple times, and will then actually
* defragment the data and send it out as one packet as soon as
* possible, but at a safer point in time
*/
while (count) {
size = count;
/* Adjust data size to the max data size */
if (size > self->max_data_size)
size = self->max_data_size;
/*
* Do we already have a buffer ready for transmit, or do
* we need to allocate a new frame
*/
if (skb) {
/*
* Any room for more data at the end of the current
* transmit buffer? Cannot use skb_tailroom, since
* dev_alloc_skb gives us a larger skb than we
* requested
* Note : use tx_data_size, because max_data_size
* may have changed and we don't want to overwrite
* the skb. - Jean II
*/
if ((tailroom = (self->tx_data_size - skb->len)) > 0) {
/* Adjust data to tailroom */
if (size > tailroom)
size = tailroom;
} else {
/*
* Current transmit frame is full, so break
* out, so we can send it as soon as possible
*/
break;
}
} else {
/* Prepare a full sized frame */
skb = alloc_skb(self->max_data_size+
self->max_header_size,
GFP_ATOMIC);
if (!skb) {
spin_unlock_irqrestore(&self->spinlock, flags);
return -ENOBUFS;
}
skb_reserve(skb, self->max_header_size);
self->tx_skb = skb;
/* Remember skb size because max_data_size may
* change later on - Jean II */
self->tx_data_size = self->max_data_size;
}
/* Copy data */
memcpy(skb_put(skb,size), buf + len, size);
count -= size;
len += size;
}
spin_unlock_irqrestore(&self->spinlock, flags);
/*
* Schedule a new thread which will transmit the frame as soon
* as possible, but at a safe point in time. We do this so the
* "user" can give us data multiple times, as PPP does (because of
* its 256 byte tx buffer). We will then defragment and send out
* all this data as one single packet.
*/
schedule_work(&self->tqueue);
return len;
}
/*
* Function ircomm_tty_write_room (tty)
*
* This routine returns the numbers of characters the tty driver will
* accept for queuing to be written. This number is subject to change as
* output buffers get emptied, or if the output flow control is acted.
*/
static int ircomm_tty_write_room(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long flags;
int ret;
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
#ifdef IRCOMM_NO_TX_BEFORE_INIT
/* max_header_size tells us if the channel is initialised or not. */
if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED)
/* Don't bother us yet */
return 0;
#endif
/* Check if we are allowed to transmit any data.
* hw_stopped is the regular flow control.
* Jean II */
if (tty->hw_stopped)
ret = 0;
else {
spin_lock_irqsave(&self->spinlock, flags);
if (self->tx_skb)
ret = self->tx_data_size - self->tx_skb->len;
else
ret = self->max_data_size;
spin_unlock_irqrestore(&self->spinlock, flags);
}
IRDA_DEBUG(2, "%s(), ret=%d\n", __func__ , ret);
return ret;
}
/*
* Function ircomm_tty_wait_until_sent (tty, timeout)
*
* This routine waits until the device has written out all of the
* characters in its transmitter FIFO.
*/
static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long orig_jiffies, poll_time;
unsigned long flags;
IRDA_DEBUG(2, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
orig_jiffies = jiffies;
/* Set poll time to 200 ms */
poll_time = IRDA_MIN(timeout, msecs_to_jiffies(200));
spin_lock_irqsave(&self->spinlock, flags);
while (self->tx_skb && self->tx_skb->len) {
spin_unlock_irqrestore(&self->spinlock, flags);
schedule_timeout_interruptible(poll_time);
spin_lock_irqsave(&self->spinlock, flags);
if (signal_pending(current))
break;
if (timeout && time_after(jiffies, orig_jiffies + timeout))
break;
}
spin_unlock_irqrestore(&self->spinlock, flags);
current->state = TASK_RUNNING;
}
/*
* Function ircomm_tty_throttle (tty)
*
* This routine notifies the tty driver that input buffers for the line
* discipline are close to full, and it should somehow signal that no
* more characters should be sent to the tty.
*/
static void ircomm_tty_throttle(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
IRDA_DEBUG(2, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
/* Software flow control? */
if (I_IXOFF(tty))
ircomm_tty_send_xchar(tty, STOP_CHAR(tty));
/* Hardware flow control? */
if (tty->termios->c_cflag & CRTSCTS) {
self->settings.dte &= ~IRCOMM_RTS;
self->settings.dte |= IRCOMM_DELTA_RTS;
ircomm_param_request(self, IRCOMM_DTE, TRUE);
}
ircomm_flow_request(self->ircomm, FLOW_STOP);
}
/*
* Function ircomm_tty_unthrottle (tty)
*
* This routine notifies the tty drivers that it should signals that
* characters can now be sent to the tty without fear of overrunning the
* input buffers of the line disciplines.
*/
static void ircomm_tty_unthrottle(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
IRDA_DEBUG(2, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
/* Using software flow control? */
if (I_IXOFF(tty)) {
ircomm_tty_send_xchar(tty, START_CHAR(tty));
}
/* Using hardware flow control? */
if (tty->termios->c_cflag & CRTSCTS) {
self->settings.dte |= (IRCOMM_RTS|IRCOMM_DELTA_RTS);
ircomm_param_request(self, IRCOMM_DTE, TRUE);
IRDA_DEBUG(1, "%s(), FLOW_START\n", __func__ );
}
ircomm_flow_request(self->ircomm, FLOW_START);
}
/*
* Function ircomm_tty_chars_in_buffer (tty)
*
* Indicates if there are any data in the buffer
*
*/
static int ircomm_tty_chars_in_buffer(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long flags;
int len = 0;
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
spin_lock_irqsave(&self->spinlock, flags);
if (self->tx_skb)
len = self->tx_skb->len;
spin_unlock_irqrestore(&self->spinlock, flags);
return len;
}
static void ircomm_tty_shutdown(struct ircomm_tty_cb *self)
{
unsigned long flags;
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
IRDA_DEBUG(0, "%s()\n", __func__ );
if (!test_and_clear_bit(ASYNC_B_INITIALIZED, &self->flags))
return;
ircomm_tty_detach_cable(self);
spin_lock_irqsave(&self->spinlock, flags);
del_timer(&self->watchdog_timer);
/* Free parameter buffer */
if (self->ctrl_skb) {
dev_kfree_skb(self->ctrl_skb);
self->ctrl_skb = NULL;
}
/* Free transmit buffer */
if (self->tx_skb) {
dev_kfree_skb(self->tx_skb);
self->tx_skb = NULL;
}
if (self->ircomm) {
ircomm_close(self->ircomm);
self->ircomm = NULL;
}
spin_unlock_irqrestore(&self->spinlock, flags);
}
/*
* Function ircomm_tty_hangup (tty)
*
* This routine notifies the tty driver that it should hangup the tty
* device.
*
*/
static void ircomm_tty_hangup(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
unsigned long flags;
IRDA_DEBUG(0, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
/* ircomm_tty_flush_buffer(tty); */
ircomm_tty_shutdown(self);
/* I guess we need to lock here - Jean II */
spin_lock_irqsave(&self->spinlock, flags);
self->flags &= ~ASYNC_NORMAL_ACTIVE;
self->tty = NULL;
self->open_count = 0;
spin_unlock_irqrestore(&self->spinlock, flags);
wake_up_interruptible(&self->open_wait);
}
/*
* Function ircomm_tty_send_xchar (tty, ch)
*
* This routine is used to send a high-priority XON/XOFF character to
* the device.
*/
static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch)
{
IRDA_DEBUG(0, "%s(), not impl\n", __func__ );
}
/*
* Function ircomm_tty_start (tty)
*
* This routine notifies the tty driver that it resume sending
* characters to the tty device.
*/
void ircomm_tty_start(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
ircomm_flow_request(self->ircomm, FLOW_START);
}
/*
* Function ircomm_tty_stop (tty)
*
* This routine notifies the tty driver that it should stop outputting
* characters to the tty device.
*/
static void ircomm_tty_stop(struct tty_struct *tty)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
ircomm_flow_request(self->ircomm, FLOW_STOP);
}
/*
* Function ircomm_check_modem_status (self)
*
* Check for any changes in the DCE's line settings. This function should
* be called whenever the dce parameter settings changes, to update the
* flow control settings and other things
*/
void ircomm_tty_check_modem_status(struct ircomm_tty_cb *self)
{
struct tty_struct *tty;
int status;
IRDA_DEBUG(0, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
tty = self->tty;
status = self->settings.dce;
if (status & IRCOMM_DCE_DELTA_ANY) {
/*wake_up_interruptible(&self->delta_msr_wait);*/
}
if ((self->flags & ASYNC_CHECK_CD) && (status & IRCOMM_DELTA_CD)) {
IRDA_DEBUG(2,
"%s(), ircomm%d CD now %s...\n", __func__ , self->line,
(status & IRCOMM_CD) ? "on" : "off");
if (status & IRCOMM_CD) {
wake_up_interruptible(&self->open_wait);
} else {
IRDA_DEBUG(2,
"%s(), Doing serial hangup..\n", __func__ );
if (tty)
tty_hangup(tty);
/* Hangup will remote the tty, so better break out */
return;
}
}
if (self->flags & ASYNC_CTS_FLOW) {
if (tty->hw_stopped) {
if (status & IRCOMM_CTS) {
IRDA_DEBUG(2,
"%s(), CTS tx start...\n", __func__ );
tty->hw_stopped = 0;
/* Wake up processes blocked on open */
wake_up_interruptible(&self->open_wait);
schedule_work(&self->tqueue);
return;
}
} else {
if (!(status & IRCOMM_CTS)) {
IRDA_DEBUG(2,
"%s(), CTS tx stop...\n", __func__ );
tty->hw_stopped = 1;
}
}
}
}
/*
* Function ircomm_tty_data_indication (instance, sap, skb)
*
* Handle incoming data, and deliver it to the line discipline
*
*/
static int ircomm_tty_data_indication(void *instance, void *sap,
struct sk_buff *skb)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
IRDA_DEBUG(2, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
IRDA_ASSERT(skb != NULL, return -1;);
if (!self->tty) {
IRDA_DEBUG(0, "%s(), no tty!\n", __func__ );
return 0;
}
/*
* If we receive data when hardware is stopped then something is wrong.
* We try to poll the peers line settings to check if we are up todate.
* Devices like WinCE can do this, and since they don't send any
* params, we can just as well declare the hardware for running.
*/
if (self->tty->hw_stopped && (self->flow == FLOW_START)) {
IRDA_DEBUG(0, "%s(), polling for line settings!\n", __func__ );
ircomm_param_request(self, IRCOMM_POLL, TRUE);
/* We can just as well declare the hardware for running */
ircomm_tty_send_initial_parameters(self);
ircomm_tty_link_established(self);
}
/*
* Use flip buffer functions since the code may be called from interrupt
* context
*/
tty_insert_flip_string(self->tty, skb->data, skb->len);
tty_flip_buffer_push(self->tty);
/* No need to kfree_skb - see ircomm_ttp_data_indication() */
return 0;
}
/*
* Function ircomm_tty_control_indication (instance, sap, skb)
*
* Parse all incoming parameters (easy!)
*
*/
static int ircomm_tty_control_indication(void *instance, void *sap,
struct sk_buff *skb)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
int clen;
IRDA_DEBUG(4, "%s()\n", __func__ );
IRDA_ASSERT(self != NULL, return -1;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
IRDA_ASSERT(skb != NULL, return -1;);
clen = skb->data[0];
irda_param_extract_all(self, skb->data+1, IRDA_MIN(skb->len-1, clen),
&ircomm_param_info);
/* No need to kfree_skb - see ircomm_control_indication() */
return 0;
}
/*
* Function ircomm_tty_flow_indication (instance, sap, cmd)
*
* This function is called by IrTTP when it wants us to slow down the
* transmission of data. We just mark the hardware as stopped, and wait
* for IrTTP to notify us that things are OK again.
*/
static void ircomm_tty_flow_indication(void *instance, void *sap,
LOCAL_FLOW cmd)
{
struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
struct tty_struct *tty;
IRDA_ASSERT(self != NULL, return;);
IRDA_ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
tty = self->tty;
switch (cmd) {
case FLOW_START:
IRDA_DEBUG(2, "%s(), hw start!\n", __func__ );
tty->hw_stopped = 0;
/* ircomm_tty_do_softint will take care of the rest */
schedule_work(&self->tqueue);
break;
default: /* If we get here, something is very wrong, better stop */
case FLOW_STOP:
IRDA_DEBUG(2, "%s(), hw stopped!\n", __func__ );
tty->hw_stopped = 1;
break;
}
self->flow = cmd;
}
#ifdef CONFIG_PROC_FS
static void ircomm_tty_line_info(struct ircomm_tty_cb *self, struct seq_file *m)
{
char sep;
seq_printf(m, "State: %s\n", ircomm_tty_state[self->state]);
seq_puts(m, "Service type: ");
if (self->service_type & IRCOMM_9_WIRE)
seq_puts(m, "9_WIRE");
else if (self->service_type & IRCOMM_3_WIRE)
seq_puts(m, "3_WIRE");
else if (self->service_type & IRCOMM_3_WIRE_RAW)
seq_puts(m, "3_WIRE_RAW");
else
seq_puts(m, "No common service type!\n");
seq_putc(m, '\n');
seq_printf(m, "Port name: %s\n", self->settings.port_name);
seq_printf(m, "DTE status:");
sep = ' ';
if (self->settings.dte & IRCOMM_RTS) {
seq_printf(m, "%cRTS", sep);
sep = '|';
}
if (self->settings.dte & IRCOMM_DTR) {
seq_printf(m, "%cDTR", sep);
sep = '|';
}
seq_putc(m, '\n');
seq_puts(m, "DCE status:");
sep = ' ';
if (self->settings.dce & IRCOMM_CTS) {
seq_printf(m, "%cCTS", sep);
sep = '|';
}
if (self->settings.dce & IRCOMM_DSR) {
seq_printf(m, "%cDSR", sep);
sep = '|';
}
if (self->settings.dce & IRCOMM_CD) {
seq_printf(m, "%cCD", sep);
sep = '|';
}
if (self->settings.dce & IRCOMM_RI) {
seq_printf(m, "%cRI", sep);
sep = '|';
}
seq_putc(m, '\n');
seq_puts(m, "Configuration: ");
if (!self->settings.null_modem)
seq_puts(m, "DTE <-> DCE\n");
else
seq_puts(m, "DTE <-> DTE (null modem emulation)\n");
seq_printf(m, "Data rate: %d\n", self->settings.data_rate);
seq_puts(m, "Flow control:");
sep = ' ';
if (self->settings.flow_control & IRCOMM_XON_XOFF_IN) {
seq_printf(m, "%cXON_XOFF_IN", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_XON_XOFF_OUT) {
seq_printf(m, "%cXON_XOFF_OUT", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_RTS_CTS_IN) {
seq_printf(m, "%cRTS_CTS_IN", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_RTS_CTS_OUT) {
seq_printf(m, "%cRTS_CTS_OUT", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_DSR_DTR_IN) {
seq_printf(m, "%cDSR_DTR_IN", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_DSR_DTR_OUT) {
seq_printf(m, "%cDSR_DTR_OUT", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_ENQ_ACK_IN) {
seq_printf(m, "%cENQ_ACK_IN", sep);
sep = '|';
}
if (self->settings.flow_control & IRCOMM_ENQ_ACK_OUT) {
seq_printf(m, "%cENQ_ACK_OUT", sep);
sep = '|';
}
seq_putc(m, '\n');
seq_puts(m, "Flags:");
sep = ' ';
if (self->flags & ASYNC_CTS_FLOW) {
seq_printf(m, "%cASYNC_CTS_FLOW", sep);
sep = '|';
}
if (self->flags & ASYNC_CHECK_CD) {
seq_printf(m, "%cASYNC_CHECK_CD", sep);
sep = '|';
}
if (self->flags & ASYNC_INITIALIZED) {
seq_printf(m, "%cASYNC_INITIALIZED", sep);
sep = '|';
}
if (self->flags & ASYNC_LOW_LATENCY) {
seq_printf(m, "%cASYNC_LOW_LATENCY", sep);
sep = '|';
}
if (self->flags & ASYNC_CLOSING) {
seq_printf(m, "%cASYNC_CLOSING", sep);
sep = '|';
}
if (self->flags & ASYNC_NORMAL_ACTIVE) {
seq_printf(m, "%cASYNC_NORMAL_ACTIVE", sep);
sep = '|';
}
seq_putc(m, '\n');
seq_printf(m, "Role: %s\n", self->client ? "client" : "server");
seq_printf(m, "Open count: %d\n", self->open_count);
seq_printf(m, "Max data size: %d\n", self->max_data_size);
seq_printf(m, "Max header size: %d\n", self->max_header_size);
if (self->tty)
seq_printf(m, "Hardware: %s\n",
self->tty->hw_stopped ? "Stopped" : "Running");
}
static int ircomm_tty_proc_show(struct seq_file *m, void *v)
{
struct ircomm_tty_cb *self;
unsigned long flags;
spin_lock_irqsave(&ircomm_tty->hb_spinlock, flags);
self = (struct ircomm_tty_cb *) hashbin_get_first(ircomm_tty);
while (self != NULL) {
if (self->magic != IRCOMM_TTY_MAGIC)
break;
ircomm_tty_line_info(self, m);
self = (struct ircomm_tty_cb *) hashbin_get_next(ircomm_tty);
}
spin_unlock_irqrestore(&ircomm_tty->hb_spinlock, flags);
return 0;
}
static int ircomm_tty_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, ircomm_tty_proc_show, NULL);
}
static const struct file_operations ircomm_tty_proc_fops = {
.owner = THIS_MODULE,
.open = ircomm_tty_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif /* CONFIG_PROC_FS */
MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>");
MODULE_DESCRIPTION("IrCOMM serial TTY driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(IRCOMM_TTY_MAJOR);
module_init(ircomm_tty_init);
module_exit(ircomm_tty_cleanup);