linux/drivers/tty/ipwireless/tty.c
Jiri Slaby 0bc1bd092a tty_port: drop last traces of low_latency
The main purpose of tty_port::low_latency was removed in commit
a9c3f68f3c (tty: Fix low_latency BUG) back in 2014. It was left in
place for drivers as an optional tune knob. But only one driver has been
using it until the previous commit. So remove this misconcept
completely, given there are no users.

Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Link: https://lore.kernel.org/r/20210105120239.28031-11-jslaby@suse.cz
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-01-07 16:17:32 +01:00

635 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* IPWireless 3G PCMCIA Network Driver
*
* Original code
* by Stephen Blackheath <stephen@blacksapphire.com>,
* Ben Martel <benm@symmetric.co.nz>
*
* Copyrighted as follows:
* Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
*
* Various driver changes and rewrites, port to new kernels
* Copyright (C) 2006-2007 Jiri Kosina
*
* Misc code cleanups and updates
* Copyright (C) 2007 David Sterba
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/ppp_defs.h>
#include <linux/if.h>
#include <linux/ppp-ioctl.h>
#include <linux/sched.h>
#include <linux/serial.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/uaccess.h>
#include "tty.h"
#include "network.h"
#include "hardware.h"
#include "main.h"
#define IPWIRELESS_PCMCIA_START (0)
#define IPWIRELESS_PCMCIA_MINORS (24)
#define IPWIRELESS_PCMCIA_MINOR_RANGE (8)
#define TTYTYPE_MODEM (0)
#define TTYTYPE_MONITOR (1)
#define TTYTYPE_RAS_RAW (2)
struct ipw_tty {
struct tty_port port;
int index;
struct ipw_hardware *hardware;
unsigned int channel_idx;
unsigned int secondary_channel_idx;
int tty_type;
struct ipw_network *network;
unsigned int control_lines;
struct mutex ipw_tty_mutex;
int tx_bytes_queued;
int closing;
};
static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS];
static struct tty_driver *ipw_tty_driver;
static char *tty_type_name(int tty_type)
{
static char *channel_names[] = {
"modem",
"monitor",
"RAS-raw"
};
return channel_names[tty_type];
}
static struct ipw_tty *get_tty(int index)
{
/*
* The 'ras_raw' channel is only available when 'loopback' mode
* is enabled.
* Number of minor starts with 16 (_RANGE * _RAS_RAW).
*/
if (!ipwireless_loopback && index >=
IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW)
return NULL;
return ttys[index];
}
static int ipw_open(struct tty_struct *linux_tty, struct file *filp)
{
struct ipw_tty *tty = get_tty(linux_tty->index);
if (!tty)
return -ENODEV;
mutex_lock(&tty->ipw_tty_mutex);
if (tty->port.count == 0)
tty->tx_bytes_queued = 0;
tty->port.count++;
tty->port.tty = linux_tty;
linux_tty->driver_data = tty;
if (tty->tty_type == TTYTYPE_MODEM)
ipwireless_ppp_open(tty->network);
mutex_unlock(&tty->ipw_tty_mutex);
return 0;
}
static void do_ipw_close(struct ipw_tty *tty)
{
tty->port.count--;
if (tty->port.count == 0) {
struct tty_struct *linux_tty = tty->port.tty;
if (linux_tty != NULL) {
tty->port.tty = NULL;
linux_tty->driver_data = NULL;
if (tty->tty_type == TTYTYPE_MODEM)
ipwireless_ppp_close(tty->network);
}
}
}
static void ipw_hangup(struct tty_struct *linux_tty)
{
struct ipw_tty *tty = linux_tty->driver_data;
if (!tty)
return;
mutex_lock(&tty->ipw_tty_mutex);
if (tty->port.count == 0) {
mutex_unlock(&tty->ipw_tty_mutex);
return;
}
do_ipw_close(tty);
mutex_unlock(&tty->ipw_tty_mutex);
}
static void ipw_close(struct tty_struct *linux_tty, struct file *filp)
{
ipw_hangup(linux_tty);
}
/* Take data received from hardware, and send it out the tty */
void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data,
unsigned int length)
{
int work = 0;
mutex_lock(&tty->ipw_tty_mutex);
if (!tty->port.count) {
mutex_unlock(&tty->ipw_tty_mutex);
return;
}
mutex_unlock(&tty->ipw_tty_mutex);
work = tty_insert_flip_string(&tty->port, data, length);
if (work != length)
printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
": %d chars not inserted to flip buffer!\n",
length - work);
if (work)
tty_flip_buffer_push(&tty->port);
}
static void ipw_write_packet_sent_callback(void *callback_data,
unsigned int packet_length)
{
struct ipw_tty *tty = callback_data;
/*
* Packet has been sent, so we subtract the number of bytes from our
* tally of outstanding TX bytes.
*/
tty->tx_bytes_queued -= packet_length;
}
static int ipw_write(struct tty_struct *linux_tty,
const unsigned char *buf, int count)
{
struct ipw_tty *tty = linux_tty->driver_data;
int room, ret;
if (!tty)
return -ENODEV;
mutex_lock(&tty->ipw_tty_mutex);
if (!tty->port.count) {
mutex_unlock(&tty->ipw_tty_mutex);
return -EINVAL;
}
room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
if (room < 0)
room = 0;
/* Don't allow caller to write any more than we have room for */
if (count > room)
count = room;
if (count == 0) {
mutex_unlock(&tty->ipw_tty_mutex);
return 0;
}
ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,
buf, count,
ipw_write_packet_sent_callback, tty);
if (ret < 0) {
mutex_unlock(&tty->ipw_tty_mutex);
return 0;
}
tty->tx_bytes_queued += count;
mutex_unlock(&tty->ipw_tty_mutex);
return count;
}
static int ipw_write_room(struct tty_struct *linux_tty)
{
struct ipw_tty *tty = linux_tty->driver_data;
int room;
/* FIXME: Exactly how is the tty object locked here .. */
if (!tty)
return -ENODEV;
if (!tty->port.count)
return -EINVAL;
room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
if (room < 0)
room = 0;
return room;
}
static int ipwireless_get_serial_info(struct tty_struct *linux_tty,
struct serial_struct *ss)
{
struct ipw_tty *tty = linux_tty->driver_data;
if (!tty)
return -ENODEV;
if (!tty->port.count)
return -EINVAL;
ss->type = PORT_UNKNOWN;
ss->line = tty->index;
ss->baud_base = 115200;
return 0;
}
static int ipwireless_set_serial_info(struct tty_struct *linux_tty,
struct serial_struct *ss)
{
return 0; /* Keeps the PCMCIA scripts happy. */
}
static int ipw_chars_in_buffer(struct tty_struct *linux_tty)
{
struct ipw_tty *tty = linux_tty->driver_data;
if (!tty)
return 0;
if (!tty->port.count)
return 0;
return tty->tx_bytes_queued;
}
static int get_control_lines(struct ipw_tty *tty)
{
unsigned int my = tty->control_lines;
unsigned int out = 0;
if (my & IPW_CONTROL_LINE_RTS)
out |= TIOCM_RTS;
if (my & IPW_CONTROL_LINE_DTR)
out |= TIOCM_DTR;
if (my & IPW_CONTROL_LINE_CTS)
out |= TIOCM_CTS;
if (my & IPW_CONTROL_LINE_DSR)
out |= TIOCM_DSR;
if (my & IPW_CONTROL_LINE_DCD)
out |= TIOCM_CD;
return out;
}
static int set_control_lines(struct ipw_tty *tty, unsigned int set,
unsigned int clear)
{
int ret;
if (set & TIOCM_RTS) {
ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1);
if (ret)
return ret;
if (tty->secondary_channel_idx != -1) {
ret = ipwireless_set_RTS(tty->hardware,
tty->secondary_channel_idx, 1);
if (ret)
return ret;
}
}
if (set & TIOCM_DTR) {
ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);
if (ret)
return ret;
if (tty->secondary_channel_idx != -1) {
ret = ipwireless_set_DTR(tty->hardware,
tty->secondary_channel_idx, 1);
if (ret)
return ret;
}
}
if (clear & TIOCM_RTS) {
ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);
if (tty->secondary_channel_idx != -1) {
ret = ipwireless_set_RTS(tty->hardware,
tty->secondary_channel_idx, 0);
if (ret)
return ret;
}
}
if (clear & TIOCM_DTR) {
ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);
if (tty->secondary_channel_idx != -1) {
ret = ipwireless_set_DTR(tty->hardware,
tty->secondary_channel_idx, 0);
if (ret)
return ret;
}
}
return 0;
}
static int ipw_tiocmget(struct tty_struct *linux_tty)
{
struct ipw_tty *tty = linux_tty->driver_data;
/* FIXME: Exactly how is the tty object locked here .. */
if (!tty)
return -ENODEV;
if (!tty->port.count)
return -EINVAL;
return get_control_lines(tty);
}
static int
ipw_tiocmset(struct tty_struct *linux_tty,
unsigned int set, unsigned int clear)
{
struct ipw_tty *tty = linux_tty->driver_data;
/* FIXME: Exactly how is the tty object locked here .. */
if (!tty)
return -ENODEV;
if (!tty->port.count)
return -EINVAL;
return set_control_lines(tty, set, clear);
}
static int ipw_ioctl(struct tty_struct *linux_tty,
unsigned int cmd, unsigned long arg)
{
struct ipw_tty *tty = linux_tty->driver_data;
if (!tty)
return -ENODEV;
if (!tty->port.count)
return -EINVAL;
/* FIXME: Exactly how is the tty object locked here .. */
if (tty->tty_type == TTYTYPE_MODEM) {
switch (cmd) {
case PPPIOCGCHAN:
{
int chan = ipwireless_ppp_channel_index(
tty->network);
if (chan < 0)
return -ENODEV;
if (put_user(chan, (int __user *) arg))
return -EFAULT;
}
return 0;
case PPPIOCGUNIT:
{
int unit = ipwireless_ppp_unit_number(
tty->network);
if (unit < 0)
return -ENODEV;
if (put_user(unit, (int __user *) arg))
return -EFAULT;
}
return 0;
case FIONREAD:
{
int val = 0;
if (put_user(val, (int __user *) arg))
return -EFAULT;
}
return 0;
case TCFLSH:
return tty_perform_flush(linux_tty, arg);
}
}
return -ENOIOCTLCMD;
}
static int add_tty(int j,
struct ipw_hardware *hardware,
struct ipw_network *network, int channel_idx,
int secondary_channel_idx, int tty_type)
{
ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL);
if (!ttys[j])
return -ENOMEM;
ttys[j]->index = j;
ttys[j]->hardware = hardware;
ttys[j]->channel_idx = channel_idx;
ttys[j]->secondary_channel_idx = secondary_channel_idx;
ttys[j]->network = network;
ttys[j]->tty_type = tty_type;
mutex_init(&ttys[j]->ipw_tty_mutex);
tty_port_init(&ttys[j]->port);
tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);
ipwireless_associate_network_tty(network, channel_idx, ttys[j]);
if (secondary_channel_idx != -1)
ipwireless_associate_network_tty(network,
secondary_channel_idx,
ttys[j]);
/* check if we provide raw device (if loopback is enabled) */
if (get_tty(j))
printk(KERN_INFO IPWIRELESS_PCCARD_NAME
": registering %s device ttyIPWp%d\n",
tty_type_name(tty_type), j);
return 0;
}
struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware,
struct ipw_network *network)
{
int i, j;
for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) {
int allfree = 1;
for (j = i; j < IPWIRELESS_PCMCIA_MINORS;
j += IPWIRELESS_PCMCIA_MINOR_RANGE)
if (ttys[j] != NULL) {
allfree = 0;
break;
}
if (allfree) {
j = i;
if (add_tty(j, hardware, network,
IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS,
TTYTYPE_MODEM))
return NULL;
j += IPWIRELESS_PCMCIA_MINOR_RANGE;
if (add_tty(j, hardware, network,
IPW_CHANNEL_DIALLER, -1,
TTYTYPE_MONITOR))
return NULL;
j += IPWIRELESS_PCMCIA_MINOR_RANGE;
if (add_tty(j, hardware, network,
IPW_CHANNEL_RAS, -1,
TTYTYPE_RAS_RAW))
return NULL;
return ttys[i];
}
}
return NULL;
}
/*
* Must be called before ipwireless_network_free().
*/
void ipwireless_tty_free(struct ipw_tty *tty)
{
int j;
struct ipw_network *network = ttys[tty->index]->network;
for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS;
j += IPWIRELESS_PCMCIA_MINOR_RANGE) {
struct ipw_tty *ttyj = ttys[j];
if (ttyj) {
mutex_lock(&ttyj->ipw_tty_mutex);
if (get_tty(j))
printk(KERN_INFO IPWIRELESS_PCCARD_NAME
": deregistering %s device ttyIPWp%d\n",
tty_type_name(ttyj->tty_type), j);
ttyj->closing = 1;
if (ttyj->port.tty != NULL) {
mutex_unlock(&ttyj->ipw_tty_mutex);
tty_vhangup(ttyj->port.tty);
/* FIXME: Exactly how is the tty object locked here
against a parallel ioctl etc */
/* FIXME2: hangup does not mean all processes
* are gone */
mutex_lock(&ttyj->ipw_tty_mutex);
}
while (ttyj->port.count)
do_ipw_close(ttyj);
ipwireless_disassociate_network_ttys(network,
ttyj->channel_idx);
tty_unregister_device(ipw_tty_driver, j);
tty_port_destroy(&ttyj->port);
ttys[j] = NULL;
mutex_unlock(&ttyj->ipw_tty_mutex);
kfree(ttyj);
}
}
}
static const struct tty_operations tty_ops = {
.open = ipw_open,
.close = ipw_close,
.hangup = ipw_hangup,
.write = ipw_write,
.write_room = ipw_write_room,
.ioctl = ipw_ioctl,
.chars_in_buffer = ipw_chars_in_buffer,
.tiocmget = ipw_tiocmget,
.tiocmset = ipw_tiocmset,
.set_serial = ipwireless_set_serial_info,
.get_serial = ipwireless_get_serial_info,
};
int ipwireless_tty_init(void)
{
int result;
ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS);
if (!ipw_tty_driver)
return -ENOMEM;
ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME;
ipw_tty_driver->name = "ttyIPWp";
ipw_tty_driver->major = 0;
ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START;
ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL;
ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
ipw_tty_driver->init_termios = tty_std_termios;
ipw_tty_driver->init_termios.c_cflag =
B9600 | CS8 | CREAD | HUPCL | CLOCAL;
ipw_tty_driver->init_termios.c_ispeed = 9600;
ipw_tty_driver->init_termios.c_ospeed = 9600;
tty_set_operations(ipw_tty_driver, &tty_ops);
result = tty_register_driver(ipw_tty_driver);
if (result) {
printk(KERN_ERR IPWIRELESS_PCCARD_NAME
": failed to register tty driver\n");
put_tty_driver(ipw_tty_driver);
return result;
}
return 0;
}
void ipwireless_tty_release(void)
{
int ret;
ret = tty_unregister_driver(ipw_tty_driver);
put_tty_driver(ipw_tty_driver);
if (ret != 0)
printk(KERN_ERR IPWIRELESS_PCCARD_NAME
": tty_unregister_driver failed with code %d\n", ret);
}
int ipwireless_tty_is_modem(struct ipw_tty *tty)
{
return tty->tty_type == TTYTYPE_MODEM;
}
void
ipwireless_tty_notify_control_line_change(struct ipw_tty *tty,
unsigned int channel_idx,
unsigned int control_lines,
unsigned int changed_mask)
{
unsigned int old_control_lines = tty->control_lines;
tty->control_lines = (tty->control_lines & ~changed_mask)
| (control_lines & changed_mask);
/*
* If DCD is de-asserted, we close the tty so pppd can tell that we
* have gone offline.
*/
if ((old_control_lines & IPW_CONTROL_LINE_DCD)
&& !(tty->control_lines & IPW_CONTROL_LINE_DCD)
&& tty->port.tty) {
tty_hangup(tty->port.tty);
}
}