alistair23-linux/drivers/staging/greybus/uart.c
Greg Kroah-Hartman 5c1ac69455 greybus: properly cleanup ida and idr structures when shutting down
idr and ida structures have internal memory allocated that needs to be
freed when modules are removed.  So call the proper idr_destroy() or
ida_destroy() functions on the module exit path to free the memory.

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Reviewed-by: Alex Elder <elder@linaro.org>
2015-07-08 11:53:43 -07:00

778 lines
18 KiB
C

/*
* UART driver for the Greybus "generic" UART module.
*
* Copyright 2014 Google Inc.
* Copyright 2014 Linaro Ltd.
*
* Released under the GPLv2 only.
*
* Heavily based on drivers/usb/class/cdc-acm.c and
* drivers/usb/serial/usb-serial.c.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/tty.h>
#include <linux/serial.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/idr.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include "greybus.h"
#define GB_NUM_MINORS 16 /* 16 is is more than enough */
#define GB_NAME "ttyGB"
struct gb_tty_line_coding {
__le32 rate;
__u8 format;
__u8 parity;
__u8 data_bits;
};
struct gb_tty {
struct tty_port port;
void *buffer;
u32 buffer_payload_max;
struct gb_connection *connection;
u16 cport_id;
unsigned int minor;
unsigned char clocal;
bool disconnected;
spinlock_t read_lock;
spinlock_t write_lock;
struct async_icount iocount;
struct async_icount oldcount;
wait_queue_head_t wioctl;
struct mutex mutex;
u8 version_major;
u8 version_minor;
u8 ctrlin; /* input control lines */
u8 ctrlout; /* output control lines */
struct gb_tty_line_coding line_coding;
};
static struct tty_driver *gb_tty_driver;
static DEFINE_IDR(tty_minors);
static DEFINE_MUTEX(table_lock);
static atomic_t reference_count = ATOMIC_INIT(0);
/* Define get_version() routine */
define_get_version(gb_tty, UART);
static int gb_uart_receive_data(struct gb_tty *gb_tty,
struct gb_connection *connection,
struct gb_uart_recv_data_request *receive_data)
{
struct tty_port *port = &gb_tty->port;
u16 recv_data_size;
int count;
unsigned long tty_flags = TTY_NORMAL;
count = gb_tty->buffer_payload_max - sizeof(*receive_data);
recv_data_size = le16_to_cpu(receive_data->size);
if (!recv_data_size || recv_data_size > count)
return -EINVAL;
if (receive_data->flags) {
if (receive_data->flags & GB_UART_RECV_FLAG_BREAK)
tty_flags = TTY_BREAK;
else if (receive_data->flags & GB_UART_RECV_FLAG_PARITY)
tty_flags = TTY_PARITY;
else if (receive_data->flags & GB_UART_RECV_FLAG_FRAMING)
tty_flags = TTY_FRAME;
/* overrun is special, not associated with a char */
if (receive_data->flags & GB_UART_RECV_FLAG_OVERRUN)
tty_insert_flip_char(port, 0, TTY_OVERRUN);
}
count = tty_insert_flip_string_fixed_flag(port, receive_data->data,
tty_flags, recv_data_size);
if (count != recv_data_size) {
dev_err(&connection->dev,
"UART: RX 0x%08x bytes only wrote 0x%08x\n",
recv_data_size, count);
}
if (count)
tty_flip_buffer_push(port);
return 0;
}
static int gb_uart_request_recv(u8 type, struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
struct gb_tty *gb_tty = connection->private;
struct gb_message *request = op->request;
struct gb_uart_serial_state_request *serial_state;
int ret = 0;
switch (type) {
case GB_UART_TYPE_RECEIVE_DATA:
ret = gb_uart_receive_data(gb_tty, connection,
request->payload);
break;
case GB_UART_TYPE_SERIAL_STATE:
serial_state = request->payload;
gb_tty->ctrlin = serial_state->control;
break;
default:
dev_err(&connection->dev,
"unsupported unsolicited request: %02x\n", type);
ret = -EINVAL;
}
return ret;
}
static int send_data(struct gb_tty *tty, u16 size, const u8 *data)
{
struct gb_uart_send_data_request *request;
int ret;
if (!data || !size)
return 0;
if (size > tty->buffer_payload_max)
size = tty->buffer_payload_max;
request = tty->buffer;
request->size = cpu_to_le16(size);
memcpy(&request->data[0], data, size);
ret = gb_operation_sync(tty->connection, GB_UART_TYPE_SEND_DATA,
request, sizeof(*request) + size, NULL, 0);
if (ret)
return ret;
else
return size;
}
static int send_line_coding(struct gb_tty *tty)
{
struct gb_uart_set_line_coding_request request;
memcpy(&request, &tty->line_coding,
sizeof(tty->line_coding));
return gb_operation_sync(tty->connection, GB_UART_TYPE_SET_LINE_CODING,
&request, sizeof(request), NULL, 0);
}
static int send_control(struct gb_tty *gb_tty, u8 control)
{
struct gb_uart_set_control_line_state_request request;
request.control = control;
return gb_operation_sync(gb_tty->connection,
GB_UART_TYPE_SET_CONTROL_LINE_STATE,
&request, sizeof(request), NULL, 0);
}
static int send_break(struct gb_tty *gb_tty, u8 state)
{
struct gb_uart_set_break_request request;
if ((state != 0) && (state != 1)) {
dev_err(&gb_tty->connection->dev,
"invalid break state of %d\n", state);
return -EINVAL;
}
request.state = state;
return gb_operation_sync(gb_tty->connection, GB_UART_TYPE_SET_BREAK,
&request, sizeof(request), NULL, 0);
}
static struct gb_tty *get_gb_by_minor(unsigned minor)
{
struct gb_tty *gb_tty;
mutex_lock(&table_lock);
gb_tty = idr_find(&tty_minors, minor);
if (gb_tty) {
mutex_lock(&gb_tty->mutex);
if (gb_tty->disconnected) {
mutex_unlock(&gb_tty->mutex);
gb_tty = NULL;
} else {
tty_port_get(&gb_tty->port);
mutex_unlock(&gb_tty->mutex);
}
}
mutex_unlock(&table_lock);
return gb_tty;
}
static int alloc_minor(struct gb_tty *gb_tty)
{
int minor;
mutex_lock(&table_lock);
minor = idr_alloc(&tty_minors, gb_tty, 0, GB_NUM_MINORS, GFP_KERNEL);
mutex_unlock(&table_lock);
if (minor >= 0)
gb_tty->minor = minor;
return minor;
}
static void release_minor(struct gb_tty *gb_tty)
{
int minor = gb_tty->minor;
gb_tty->minor = 0; /* Maybe should use an invalid value instead */
mutex_lock(&table_lock);
idr_remove(&tty_minors, minor);
mutex_unlock(&table_lock);
}
static int gb_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct gb_tty *gb_tty;
int retval;
gb_tty = get_gb_by_minor(tty->index);
if (!gb_tty)
return -ENODEV;
retval = tty_standard_install(driver, tty);
if (retval)
goto error;
tty->driver_data = gb_tty;
return 0;
error:
tty_port_put(&gb_tty->port);
return retval;
}
static int gb_tty_open(struct tty_struct *tty, struct file *file)
{
struct gb_tty *gb_tty = tty->driver_data;
return tty_port_open(&gb_tty->port, tty, file);
}
static void gb_tty_close(struct tty_struct *tty, struct file *file)
{
struct gb_tty *gb_tty = tty->driver_data;
tty_port_close(&gb_tty->port, tty, file);
}
static void gb_tty_cleanup(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
tty_port_put(&gb_tty->port);
}
static void gb_tty_hangup(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
tty_port_hangup(&gb_tty->port);
}
static int gb_tty_write(struct tty_struct *tty, const unsigned char *buf,
int count)
{
struct gb_tty *gb_tty = tty->driver_data;
return send_data(gb_tty, count, buf);
}
static int gb_tty_write_room(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
return gb_tty->buffer_payload_max;
}
static int gb_tty_chars_in_buffer(struct tty_struct *tty)
{
return 0;
}
static int gb_tty_break_ctl(struct tty_struct *tty, int state)
{
struct gb_tty *gb_tty = tty->driver_data;
return send_break(gb_tty, state ? 1 : 0);
}
static void gb_tty_set_termios(struct tty_struct *tty,
struct ktermios *termios_old)
{
struct gb_tty *gb_tty = tty->driver_data;
struct ktermios *termios = &tty->termios;
struct gb_tty_line_coding newline;
u8 newctrl = gb_tty->ctrlout;
newline.rate = cpu_to_le32(tty_get_baud_rate(tty));
newline.format = termios->c_cflag & CSTOPB ?
GB_SERIAL_2_STOP_BITS : GB_SERIAL_1_STOP_BITS;
newline.parity = termios->c_cflag & PARENB ?
(termios->c_cflag & PARODD ? 1 : 2) +
(termios->c_cflag & CMSPAR ? 2 : 0) : 0;
switch (termios->c_cflag & CSIZE) {
case CS5:
newline.data_bits = 5;
break;
case CS6:
newline.data_bits = 6;
break;
case CS7:
newline.data_bits = 7;
break;
case CS8:
default:
newline.data_bits = 8;
break;
}
/* FIXME: needs to clear unsupported bits in the termios */
gb_tty->clocal = ((termios->c_cflag & CLOCAL) != 0);
if (C_BAUD(tty) == B0) {
newline.rate = gb_tty->line_coding.rate;
newctrl &= GB_UART_CTRL_DTR;
} else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) {
newctrl |= GB_UART_CTRL_DTR;
}
if (newctrl != gb_tty->ctrlout) {
gb_tty->ctrlout = newctrl;
send_control(gb_tty, newctrl);
}
if (memcpy(&gb_tty->line_coding, &newline, sizeof(newline))) {
memcpy(&gb_tty->line_coding, &newline, sizeof(newline));
send_line_coding(gb_tty);
}
}
static int gb_tty_tiocmget(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
return (gb_tty->ctrlout & GB_UART_CTRL_DTR ? TIOCM_DTR : 0) |
(gb_tty->ctrlout & GB_UART_CTRL_RTS ? TIOCM_RTS : 0) |
(gb_tty->ctrlin & GB_UART_CTRL_DSR ? TIOCM_DSR : 0) |
(gb_tty->ctrlin & GB_UART_CTRL_RI ? TIOCM_RI : 0) |
(gb_tty->ctrlin & GB_UART_CTRL_DCD ? TIOCM_CD : 0) |
TIOCM_CTS;
}
static int gb_tty_tiocmset(struct tty_struct *tty, unsigned int set,
unsigned int clear)
{
struct gb_tty *gb_tty = tty->driver_data;
u8 newctrl = gb_tty->ctrlout;
set = (set & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) |
(set & TIOCM_RTS ? GB_UART_CTRL_RTS : 0);
clear = (clear & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) |
(clear & TIOCM_RTS ? GB_UART_CTRL_RTS : 0);
newctrl = (newctrl & ~clear) | set;
if (gb_tty->ctrlout == newctrl)
return 0;
gb_tty->ctrlout = newctrl;
return send_control(gb_tty, newctrl);
}
static void gb_tty_throttle(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
unsigned char stop_char;
int retval;
if (I_IXOFF(tty)) {
stop_char = STOP_CHAR(tty);
retval = gb_tty_write(tty, &stop_char, 1);
if (retval <= 0)
return;
}
if (tty->termios.c_cflag & CRTSCTS) {
gb_tty->ctrlout &= ~GB_UART_CTRL_RTS;
retval = send_control(gb_tty, gb_tty->ctrlout);
}
}
static void gb_tty_unthrottle(struct tty_struct *tty)
{
struct gb_tty *gb_tty = tty->driver_data;
unsigned char start_char;
int retval;
if (I_IXOFF(tty)) {
start_char = START_CHAR(tty);
retval = gb_tty_write(tty, &start_char, 1);
if (retval <= 0)
return;
}
if (tty->termios.c_cflag & CRTSCTS) {
gb_tty->ctrlout |= GB_UART_CTRL_RTS;
retval = send_control(gb_tty, gb_tty->ctrlout);
}
}
static int get_serial_info(struct gb_tty *gb_tty,
struct serial_struct __user *info)
{
struct serial_struct tmp;
if (!info)
return -EINVAL;
memset(&tmp, 0, sizeof(tmp));
tmp.flags = ASYNC_LOW_LATENCY | ASYNC_SKIP_TEST;
tmp.type = PORT_16550A;
tmp.line = gb_tty->minor;
tmp.xmit_fifo_size = 16;
tmp.baud_base = 9600;
tmp.close_delay = gb_tty->port.close_delay / 10;
tmp.closing_wait = gb_tty->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
ASYNC_CLOSING_WAIT_NONE : gb_tty->port.closing_wait / 10;
if (copy_to_user(info, &tmp, sizeof(tmp)))
return -EFAULT;
return 0;
}
static int set_serial_info(struct gb_tty *gb_tty,
struct serial_struct __user *newinfo)
{
struct serial_struct new_serial;
unsigned int closing_wait;
unsigned int close_delay;
int retval = 0;
if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
return -EFAULT;
close_delay = new_serial.close_delay * 10;
closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
mutex_lock(&gb_tty->port.mutex);
if (!capable(CAP_SYS_ADMIN)) {
if ((close_delay != gb_tty->port.close_delay) ||
(closing_wait != gb_tty->port.closing_wait))
retval = -EPERM;
else
retval = -EOPNOTSUPP;
} else {
gb_tty->port.close_delay = close_delay;
gb_tty->port.closing_wait = closing_wait;
}
mutex_unlock(&gb_tty->port.mutex);
return retval;
}
static int wait_serial_change(struct gb_tty *gb_tty, unsigned long arg)
{
int retval = 0;
DECLARE_WAITQUEUE(wait, current);
struct async_icount old;
struct async_icount new;
if (!(arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD)))
return -EINVAL;
do {
spin_lock_irq(&gb_tty->read_lock);
old = gb_tty->oldcount;
new = gb_tty->iocount;
gb_tty->oldcount = new;
spin_unlock_irq(&gb_tty->read_lock);
if ((arg & TIOCM_DSR) && (old.dsr != new.dsr))
break;
if ((arg & TIOCM_CD) && (old.dcd != new.dcd))
break;
if ((arg & TIOCM_RI) && (old.rng != new.rng))
break;
add_wait_queue(&gb_tty->wioctl, &wait);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
remove_wait_queue(&gb_tty->wioctl, &wait);
if (gb_tty->disconnected) {
if (arg & TIOCM_CD)
break;
retval = -ENODEV;
} else if (signal_pending(current)) {
retval = -ERESTARTSYS;
}
} while (!retval);
return retval;
}
static int get_serial_usage(struct gb_tty *gb_tty,
struct serial_icounter_struct __user *count)
{
struct serial_icounter_struct icount;
int retval = 0;
memset(&icount, 0, sizeof(icount));
icount.dsr = gb_tty->iocount.dsr;
icount.rng = gb_tty->iocount.rng;
icount.dcd = gb_tty->iocount.dcd;
icount.frame = gb_tty->iocount.frame;
icount.overrun = gb_tty->iocount.overrun;
icount.parity = gb_tty->iocount.parity;
icount.brk = gb_tty->iocount.brk;
if (copy_to_user(count, &icount, sizeof(icount)) > 0)
retval = -EFAULT;
return retval;
}
static int gb_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
struct gb_tty *gb_tty = tty->driver_data;
switch (cmd) {
case TIOCGSERIAL:
return get_serial_info(gb_tty,
(struct serial_struct __user *)arg);
case TIOCSSERIAL:
return set_serial_info(gb_tty,
(struct serial_struct __user *)arg);
case TIOCMIWAIT:
return wait_serial_change(gb_tty, arg);
case TIOCGICOUNT:
return get_serial_usage(gb_tty,
(struct serial_icounter_struct __user *)arg);
}
return -ENOIOCTLCMD;
}
static const struct tty_operations gb_ops = {
.install = gb_tty_install,
.open = gb_tty_open,
.close = gb_tty_close,
.cleanup = gb_tty_cleanup,
.hangup = gb_tty_hangup,
.write = gb_tty_write,
.write_room = gb_tty_write_room,
.ioctl = gb_tty_ioctl,
.throttle = gb_tty_throttle,
.unthrottle = gb_tty_unthrottle,
.chars_in_buffer = gb_tty_chars_in_buffer,
.break_ctl = gb_tty_break_ctl,
.set_termios = gb_tty_set_termios,
.tiocmget = gb_tty_tiocmget,
.tiocmset = gb_tty_tiocmset,
};
static struct tty_port_operations null_ops = { };
static int gb_tty_init(void);
static void gb_tty_exit(void);
static int gb_uart_connection_init(struct gb_connection *connection)
{
struct gb_tty *gb_tty;
struct device *tty_dev;
int retval;
int minor;
/* First time here, initialize the tty structures */
if (atomic_inc_return(&reference_count) == 1) {
retval = gb_tty_init();
if (retval) {
atomic_dec(&reference_count);
return retval;
}
}
gb_tty = kzalloc(sizeof(*gb_tty), GFP_KERNEL);
if (!gb_tty) {
retval = -ENOMEM;
goto error_alloc;
}
gb_tty->buffer_payload_max =
gb_operation_get_payload_size_max(connection);
if (!gb_tty->buffer_payload_max) {
retval = -EINVAL;
goto error_payload;
}
gb_tty->buffer = kzalloc(gb_tty->buffer_payload_max, GFP_KERNEL);
if (!gb_tty->buffer) {
retval = -ENOMEM;
goto error_payload;
}
gb_tty->connection = connection;
connection->private = gb_tty;
/* Check for compatible protocol version */
retval = get_version(gb_tty);
if (retval)
goto error_version;
minor = alloc_minor(gb_tty);
if (minor < 0) {
if (minor == -ENOSPC) {
dev_err(&connection->dev,
"no more free minor numbers\n");
retval = -ENODEV;
goto error_version;
}
retval = minor;
goto error_version;
}
gb_tty->minor = minor;
spin_lock_init(&gb_tty->write_lock);
spin_lock_init(&gb_tty->read_lock);
init_waitqueue_head(&gb_tty->wioctl);
mutex_init(&gb_tty->mutex);
tty_port_init(&gb_tty->port);
gb_tty->port.ops = &null_ops;
send_control(gb_tty, gb_tty->ctrlout);
/* initialize the uart to be 9600n81 */
gb_tty->line_coding.rate = cpu_to_le32(9600);
gb_tty->line_coding.format = GB_SERIAL_1_STOP_BITS;
gb_tty->line_coding.parity = GB_SERIAL_NO_PARITY;
gb_tty->line_coding.data_bits = 8;
send_line_coding(gb_tty);
tty_dev = tty_port_register_device(&gb_tty->port, gb_tty_driver, minor,
&connection->dev);
if (IS_ERR(tty_dev)) {
retval = PTR_ERR(tty_dev);
goto error;
}
return 0;
error:
tty_port_destroy(&gb_tty->port);
release_minor(gb_tty);
error_version:
connection->private = NULL;
kfree(gb_tty->buffer);
error_payload:
kfree(gb_tty);
error_alloc:
if (atomic_dec_return(&reference_count) == 0)
gb_tty_exit();
return retval;
}
static void gb_uart_connection_exit(struct gb_connection *connection)
{
struct gb_tty *gb_tty = connection->private;
struct tty_struct *tty;
if (!gb_tty)
return;
mutex_lock(&gb_tty->mutex);
gb_tty->disconnected = true;
wake_up_all(&gb_tty->wioctl);
connection->private = NULL;
mutex_unlock(&gb_tty->mutex);
tty = tty_port_tty_get(&gb_tty->port);
if (tty) {
tty_vhangup(tty);
tty_kref_put(tty);
}
/* FIXME - stop all traffic */
tty_unregister_device(gb_tty_driver, gb_tty->minor);
/* FIXME - free transmit / receive buffers */
tty_port_put(&gb_tty->port);
tty_port_destroy(&gb_tty->port);
kfree(gb_tty->buffer);
kfree(gb_tty);
/* If last device is gone, tear down the tty structures */
if (atomic_dec_return(&reference_count) == 0)
gb_tty_exit();
}
static int gb_tty_init(void)
{
int retval = 0;
gb_tty_driver = tty_alloc_driver(GB_NUM_MINORS, 0);
if (IS_ERR(gb_tty_driver)) {
pr_err("Can not allocate tty driver\n");
retval = -ENOMEM;
goto fail_unregister_dev;
}
gb_tty_driver->driver_name = "gb";
gb_tty_driver->name = GB_NAME;
gb_tty_driver->major = 0;
gb_tty_driver->minor_start = 0;
gb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
gb_tty_driver->subtype = SERIAL_TYPE_NORMAL;
gb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
gb_tty_driver->init_termios = tty_std_termios;
gb_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
tty_set_operations(gb_tty_driver, &gb_ops);
retval = tty_register_driver(gb_tty_driver);
if (retval) {
pr_err("Can not register tty driver: %d\n", retval);
goto fail_put_gb_tty;
}
return 0;
fail_put_gb_tty:
put_tty_driver(gb_tty_driver);
fail_unregister_dev:
return retval;
}
static void gb_tty_exit(void)
{
tty_unregister_driver(gb_tty_driver);
put_tty_driver(gb_tty_driver);
idr_destroy(&tty_minors);
}
static struct gb_protocol uart_protocol = {
.name = "uart",
.id = GREYBUS_PROTOCOL_UART,
.major = 0,
.minor = 1,
.connection_init = gb_uart_connection_init,
.connection_exit = gb_uart_connection_exit,
.request_recv = gb_uart_request_recv,
};
gb_builtin_protocol_driver(uart_protocol);