// SPDX-License-Identifier: GPL-2.0
/* vcc.c: sun4v virtual channel concentrator
*
* Copyright (C) 2017 Oracle. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <asm/vio.h>
#include <asm/ldc.h>
#define DRV_MODULE_NAME "vcc"
#define DRV_MODULE_VERSION "1.1"
#define DRV_MODULE_RELDATE "July 1, 2017"
static char version[] =
DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")";
MODULE_DESCRIPTION("Sun LDOM virtual console concentrator driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_MODULE_VERSION);
struct vcc_port {
struct vio_driver_state vio;
spinlock_t lock;
char *domain;
struct tty_struct *tty; /* only populated while dev is open */
unsigned long index; /* index into the vcc_table */
u64 refcnt;
bool excl_locked;
bool removed;
/* This buffer is required to support the tty write_room interface
* and guarantee that any characters that the driver accepts will
* be eventually sent, either immediately or later.
*/
int chars_in_buffer;
struct vio_vcc buffer;
struct timer_list rx_timer;
struct timer_list tx_timer;
};
/* Microseconds that thread will delay waiting for a vcc port ref */
#define VCC_REF_DELAY 100
#define VCC_MAX_PORTS 1024
#define VCC_MINOR_START 0 /* must be zero */
#define VCC_BUFF_LEN VIO_VCC_MTU_SIZE
#define VCC_CTL_BREAK -1
#define VCC_CTL_HUP -2
static const char vcc_driver_name[] = "vcc";
static const char vcc_device_node[] = "vcc";
static struct tty_driver *vcc_tty_driver;
static struct vcc_port *vcc_table[VCC_MAX_PORTS];
static DEFINE_SPINLOCK(vcc_table_lock);
int vcc_dbg;
int vcc_dbg_ldc;
int vcc_dbg_vio;
module_param(vcc_dbg, uint, 0664);
module_param(vcc_dbg_ldc, uint, 0664);
module_param(vcc_dbg_vio, uint, 0664);
#define VCC_DBG_DRV 0x1
#define VCC_DBG_LDC 0x2
#define VCC_DBG_PKT 0x4
#define vccdbg(f, a...) \
do { \
if (vcc_dbg & VCC_DBG_DRV) \
pr_info(f, ## a); \
} while (0) \
#define vccdbgl(l) \
do { \
if (vcc_dbg & VCC_DBG_LDC) \
ldc_print(l); \
} while (0) \
#define vccdbgp(pkt) \
do { \
if (vcc_dbg & VCC_DBG_PKT) { \
int i; \
for (i = 0; i < pkt.tag.stype; i++) \
pr_info("[%c]", pkt.data[i]); \
} \
} while (0) \
/* Note: Be careful when adding flags to this line discipline. Don't
* add anything that will cause echoing or we'll go into recursive
* loop echoing chars back and forth with the console drivers.
*/
static const struct ktermios vcc_tty_termios = {
.c_iflag = IGNBRK | IGNPAR,
.c_oflag = OPOST,
.c_cflag = B38400 | CS8 | CREAD | HUPCL,
.c_cc = INIT_C_CC,
.c_ispeed = 38400,
.c_ospeed = 38400
};
/**
* vcc_table_add() - Add VCC port to the VCC table
* @port: pointer to the VCC port
*
* Return: index of the port in the VCC table on success,
* -1 on failure
*/
static int vcc_table_add(struct vcc_port *port)
{
unsigned long flags;
int i;
spin_lock_irqsave(&vcc_table_lock, flags);
for (i = VCC_MINOR_START; i < VCC_MAX_PORTS; i++) {
if (!vcc_table[i]) {
vcc_table[i] = port;
break;
}
}
spin_unlock_irqrestore(&vcc_table_lock, flags);
if (i < VCC_MAX_PORTS)
return i;
else
return -1;
}
/**
* vcc_table_remove() - Removes a VCC port from the VCC table
* @index: Index into the VCC table
*/
static void vcc_table_remove(unsigned long index)
{
unsigned long flags;
if (WARN_ON(index >= VCC_MAX_PORTS))
return;
spin_lock_irqsave(&vcc_table_lock, flags);
vcc_table[index] = NULL;
spin_unlock_irqrestore(&vcc_table_lock, flags);
}
/**
* vcc_get() - Gets a reference to VCC port
* @index: Index into the VCC table
* @excl: Indicates if an exclusive access is requested
*
* Return: reference to the VCC port, if found
* NULL, if port not found
*/
static struct vcc_port *vcc_get(unsigned long index, bool excl)
{
struct vcc_port *port;
unsigned long flags;
try_again:
spin_lock_irqsave(&vcc_table_lock, flags);
port = vcc_table[index];
if (!port) {
spin_unlock_irqrestore(&vcc_table_lock, flags);
return NULL;
}
if (!excl) {
if (port->excl_locked) {
spin_unlock_irqrestore(&vcc_table_lock, flags);
udelay(VCC_REF_DELAY);
goto try_again;
}
port->refcnt++;
spin_unlock_irqrestore(&vcc_table_lock, flags);
return port;
}
if (port->refcnt) {
spin_unlock_irqrestore(&vcc_table_lock, flags);
/* Threads wanting exclusive access will wait half the time,
* probably giving them higher priority in the case of
* multiple waiters.
*/
udelay(VCC_REF_DELAY/2);
goto try_again;
}
port->refcnt++;
port