// SPDX-License-Identifier: GPL-2.0
/*
* UART driver for the Greybus "generic" UART module.
*
* Copyright 2014 Google Inc.
* Copyright 2014 Linaro Ltd.
*
* 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/signal.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/idr.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/greybus.h>
#include "gbphy.h"
#define GB_NUM_MINORS 16 /* 16 is more than enough */
#define GB_NAME "ttyGB"
#define GB_UART_WRITE_FIFO_SIZE PAGE_SIZE
#define GB_UART_WRITE_ROOM_MARGIN 1 /* leave some space in fifo */
#define GB_UART_FIRMWARE_CREDITS 4096
#define GB_UART_CREDIT_WAIT_TIMEOUT_MSEC 10000
struct gb_tty {
struct gbphy_device *gbphy_dev;
struct tty_port port;
void *buffer;
size_t 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 ctrlin; /* input control lines */
u8 ctrlout; /* output control lines */
struct gb_uart_set_line_coding_request line_coding;
struct work_struct tx_work;
struct kfifo write_fifo;
bool close_pending;
unsigned int credits;
struct completion credits_complete;
};
static struct tty_driver *gb_tty_driver;
static DEFINE_IDR(tty_minors);
static DEFINE_MUTEX(table_lock);
static int gb_uart_receive_data_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
struct gb_tty *gb_tty = gb_connection_get_data(connection);
struct tty_port *port = &gb_tty->port;
struct gb_message *request = op->request;
struct gb_uart_recv_data_request *receive_data;
u16 recv_data_size;
int count;
unsigned long tty_flags = TTY_NORMAL;
if (request->payload_size < sizeof(*receive_data)) {
dev_err(&gb_tty->gbphy_dev->dev,
"short receive-data request received (%zu < %zu)\n",
request->payload_size, sizeof(*receive_data));
return -EINVAL;
}
receive_data = op->request->payload;
recv_data_size = le16_to_cpu(receive_data->size);
if (recv_data_size != request->payload_size - sizeof(*receive_data)) {
dev_err(&gb_tty->gbphy_dev->dev,
"malformed receive-data request received (%u != %zu)\n",
recv_data_size,
request->payload_size - sizeof(*receive_data));
return -EINVAL;
}
if (!recv_data_size)
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(&gb_tty->gbphy_dev->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_serial_state_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
struct gb_tty *gb_tty = gb_connection_get_data(connection);
struct gb_message *request = op->request;
struct gb_uart_serial_state_request *serial_state;
if (request->payload_size < sizeof(*serial_state)) {
dev_err(&gb_tty->gbphy_dev->dev,
"short serial-state event received (%zu < %zu)\n",
request->payload_size, sizeof(*serial_state));
return -EINVAL;
}
serial_state = request->payload;
gb_tty->ctrlin = serial_state->control;
return 0;
}
static int gb_uart_receive_credits_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
struct gb_tty *gb_tty = gb_connection_get_data(connection);
struct gb_message *request = op->request;
struct gb_uart_receive_credits_request *credit_request;
unsigned long flags;
unsigned int incoming_credits;
int ret = 0;
if (request->payload_size < sizeof(*credit_request)) {
dev_err(&gb_tty->gbphy_dev->dev,
"short receive_credits event received (%zu < %zu)\n",
request->payload_size,
sizeof(*credit_request));
return -EINVAL;
}
credit_request = request->payload;
incoming_credits = le16_to_cpu(credit_request->count);
spin_lock_irqsave(&gb_tty->write_lock, flags);
gb_tty->credits += incoming_credits;
if (gb_tty->credits >