18.4. ioctls
The ioctl
function callback in the
struct tty_driver is called by the tty core when
ioctl(2) is called on the device node. If the
tty driver does not know how to handle the ioctl
value passed to it, it should return -ENOIOCTLCMD
to try to let the tty core implement a generic version of the call.
The 2.6 kernel defines about 70 different tty
ioctls that can be be sent to a tty driver. Most
tty drivers do not handle all of these, but only a small subset of
the more common ones. Here is a list of the more popular tty
ioctls, what they mean, and how to implement
them:
- TIOCSERGETLSR
-
Gets the value of this tty device's
line
status register (LSR).
- TIOCGSERIAL
-
Gets the serial line information. A caller can potentially get a lot
of serial line information from the tty device all at once in this
call. Some programs (such as setserial and
dip) call this function to make sure that the baud
rate was properly set and to get general information on what type of
device the tty driver controls. The caller passes in a pointer to a
large struct of type serial_struct, which the tty
driver should fill up with the proper values. Here is an example of
how this can be implemented:
static int tiny_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tiny_serial *tiny = tty->driver_data;
if (cmd = = TIOCGSERIAL) {
struct serial_struct tmp;
if (!arg)
return -EFAULT;
memset(&tmp, 0, sizeof(tmp));
tmp.type = tiny->serial.type;
tmp.line = tiny->serial.line;
tmp.port = tiny->serial.port;
tmp.irq = tiny->serial.irq;
tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size;
tmp.baud_base = tiny->serial.baud_base;
tmp.close_delay = 5*HZ;
tmp.closing_wait = 30*HZ;
tmp.custom_divisor = tiny->serial.custom_divisor;
tmp.hub6 = tiny->serial.hub6;
tmp.io_type = tiny->serial.io_type;
if (copy_to_user((void _ _user *)arg, &tmp, sizeof(tmp)))
return -EFAULT;
return 0;
}
return -ENOIOCTLCMD;
}
- TIOCSSERIAL
-
Sets the
serial line information. This is the
opposite of TIOCGSERIAL and allows the user to set
the serial line status of the tty device all at once. A pointer to a
struct serial_struct is passed
to this call, full of data that the tty device should now be set to.
If the tty driver does not implement this call, most programs still
works properly.
- TIOCMIWAIT
-
Waits for MSR change. The user asks for this
ioctl in the unusual circumstances that it wants
to sleep within the kernel until something happens to the
MSR
register of the tty device. The arg parameter
contains the type of event that the user is waiting for. This is
commonly used to wait until a status line changes, signaling that
more data is ready to be sent to the device.
Be careful when implementing this ioctl, and do
not use the interruptible_sleep_on call, as it
is unsafe (there are lots of nasty race conditions involved with it).
Instead, a wait_queue should be used to avoid
these problems. Here's an example of how to
implement this ioctl:
static int tiny_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tiny_serial *tiny = tty->driver_data;
if (cmd = = TIOCMIWAIT) {
DECLARE_WAITQUEUE(wait, current);
struct async_icount cnow;
struct async_icount cprev;
cprev = tiny->icount;
while (1) {
add_wait_queue(&tiny->wait, &wait);
set_current_state(TASK_INTERRUPTIBLE);
schedule( );
remove_wait_queue(&tiny->wait, &wait);
/* see if a signal woke us up */
if (signal_pending(current))
return -ERESTARTSYS;
cnow = tiny->icount;
if (cnow.rng = = cprev.rng && cnow.dsr = = cprev.dsr &&
cnow.dcd = = cprev.dcd && cnow.cts = = cprev.cts)
return -EIO; /* no change => error */
if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
return 0;
}
cprev = cnow;
}
}
return -ENOIOCTLCMD;
}
Somewhere in the tty driver's code that recognizes
that the MSR register changes, the following line must be called for
this code to work properly:
wake_up_interruptible(&tp->wait);
- TIOCGICOUNT
-
Gets
interrupt counts. This is called when the user wants to know how many
serial line interrupts have happened. If the driver has an interrupt
handler, it should define an internal structure of counters to keep
track of these statistics and increment the proper counter every time
the function is run by the kernel.
This ioctl call passes the kernel a pointer to a
structure
serial_icounter_struct
,
which should be filled by the tty driver. This call is often made in
conjunction with the previous TIOCMIWAIT
ioctl call. If the tty driver keeps track of all
of these interrupts while the driver is operating, the code to
implement this call can be very simple:
static int tiny_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tiny_serial *tiny = tty->driver_data;
if (cmd = = TIOCGICOUNT) {
struct async_icount cnow = tiny->icount;
struct serial_icounter_struct icount;
icount.cts = cnow.cts;
icount.dsr = cnow.dsr;
icount.rng = cnow.rng;
icount.dcd = cnow.dcd;
icount.rx = cnow.rx;
icount.tx = cnow.tx;
icount.frame = cnow.frame;
icount.overrun = cnow.overrun;
icount.parity = cnow.parity;
icount.brk = cnow.brk;
icount.buf_overrun = cnow.buf_overrun;
if (copy_to_user((void _ _user *)arg, &icount, sizeof(icount)))
return -EFAULT;
return 0;
}
return -ENOIOCTLCMD;
}
|