1091 lines · 22.2 KB · C
RawDownload
// 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 <linux/termios_internal.h>#include <asm/vio.h>#include <asm/ldc.h> MODULE_DESCRIPTION("Sun LDOM virtual console concentrator driver");MODULE_LICENSE("GPL");MODULE_VERSION("1.1"); 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.	 */	size_t 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 struct tty_driver *vcc_tty_driver; static struct vcc_port *vcc_table[VCC_MAX_PORTS];static DEFINE_SPINLOCK(vcc_table_lock); static unsigned int vcc_dbg;static unsigned int vcc_dbg_ldc;static unsigned 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->excl_locked = true;	spin_unlock_irqrestore(&vcc_table_lock, flags); 	return port;} /** * vcc_put() - Returns a reference to VCC port * @port: pointer to VCC port * @excl: Indicates if the returned reference is an exclusive reference * * Note: It's the caller's responsibility to ensure the correct value *	 for the excl flag */static void vcc_put(struct vcc_port *port, bool excl){	unsigned long flags; 	if (!port)		return; 	spin_lock_irqsave(&vcc_table_lock, flags); 	/* check if caller attempted to put with the wrong flags */	if (WARN_ON((excl && !port->excl_locked) ||		    (!excl && port->excl_locked)))		goto done; 	port->refcnt--; 	if (excl)		port->excl_locked = false; done:	spin_unlock_irqrestore(&vcc_table_lock, flags);} /** * vcc_get_ne() - Get a non-exclusive reference to VCC port * @index: Index into the VCC table * * Gets a non-exclusive reference to VCC port, if it's not removed * * Return: pointer to the VCC port, if found *	   NULL, if port not found */static struct vcc_port *vcc_get_ne(unsigned long index){	struct vcc_port *port; 	port = vcc_get(index, false); 	if (port && port->removed) {		vcc_put(port, false);		return NULL;	} 	return port;} static void vcc_kick_rx(struct vcc_port *port){	struct vio_driver_state *vio = &port->vio; 	assert_spin_locked(&port->lock); 	if (!timer_pending(&port->rx_timer) && !port->removed) {		disable_irq_nosync(vio->vdev->rx_irq);		port->rx_timer.expires = (jiffies + 1);		add_timer(&port->rx_timer);	}} static void vcc_kick_tx(struct vcc_port *port){	assert_spin_locked(&port->lock); 	if (!timer_pending(&port->tx_timer) && !port->removed) {		port->tx_timer.expires = (jiffies + 1);		add_timer(&port->tx_timer);	}} static int vcc_rx_check(struct tty_struct *tty, int size){	if (WARN_ON(!tty || !tty->port))		return 1; 	/* tty_buffer_request_room won't sleep because it uses	 * GFP_ATOMIC flag to allocate buffer	 */	if (test_bit(TTY_THROTTLED, &tty->flags) ||	    (tty_buffer_request_room(tty->port, VCC_BUFF_LEN) < VCC_BUFF_LEN))		return 0; 	return 1;} static int vcc_rx(struct tty_struct *tty, char *buf, int size){	int len = 0; 	if (WARN_ON(!tty || !tty->port))		return len; 	len = tty_insert_flip_string(tty->port, buf, size);	if (len)		tty_flip_buffer_push(tty->port); 	return len;} static int vcc_ldc_read(struct vcc_port *port){	struct vio_driver_state *vio = &port->vio;	struct tty_struct *tty;	struct vio_vcc pkt;	int rv = 0; 	tty = port->tty;	if (!tty) {		rv = ldc_rx_reset(vio->lp);		vccdbg("VCC: reset rx q: rv=%d\n", rv);		goto done;	} 	/* Read as long as LDC has incoming data. */	while (1) {		if (!vcc_rx_check(tty, VIO_VCC_MTU_SIZE)) {			vcc_kick_rx(port);			break;		} 		vccdbgl(vio->lp); 		rv = ldc_read(vio->lp, &pkt, sizeof(pkt));		if (rv <= 0)			break; 		vccdbg("VCC: ldc_read()=%d\n", rv);		vccdbg("TAG [%02x:%02x:%04x:%08x]\n",		       pkt.tag.type, pkt.tag.stype,		       pkt.tag.stype_env, pkt.tag.sid); 		if (pkt.tag.type == VIO_TYPE_DATA) {			vccdbgp(pkt);			/* vcc_rx_check ensures memory availability */			vcc_rx(tty, pkt.data, pkt.tag.stype);		} else {			pr_err("VCC: unknown msg [%02x:%02x:%04x:%08x]\n",			       pkt.tag.type, pkt.tag.stype,			       pkt.tag.stype_env, pkt.tag.sid);			rv = -ECONNRESET;			break;		} 		WARN_ON(rv != LDC_PACKET_SIZE);	} done:	return rv;} static void vcc_rx_timer(struct timer_list *t){	struct vcc_port *port = timer_container_of(port, t, rx_timer);	struct vio_driver_state *vio;	unsigned long flags;	int rv; 	spin_lock_irqsave(&port->lock, flags);	port->rx_timer.expires = 0; 	vio = &port->vio; 	enable_irq(vio->vdev->rx_irq); 	if (!port->tty || port->removed)		goto done; 	rv = vcc_ldc_read(port);	if (rv == -ECONNRESET)		vio_conn_reset(vio); done:	spin_unlock_irqrestore(&port->lock, flags);	vcc_put(port, false);} static void vcc_tx_timer(struct timer_list *t){	struct vcc_port *port = timer_container_of(port, t, tx_timer);	struct vio_vcc *pkt;	unsigned long flags;	size_t tosend = 0;	int rv; 	spin_lock_irqsave(&port->lock, flags);	port->tx_timer.expires = 0; 	if (!port->tty || port->removed)		goto done; 	tosend = min(VCC_BUFF_LEN, port->chars_in_buffer);	if (!tosend)		goto done; 	pkt = &port->buffer;	pkt->tag.type = VIO_TYPE_DATA;	pkt->tag.stype = tosend;	vccdbgl(port->vio.lp); 	rv = ldc_write(port->vio.lp, pkt, (VIO_TAG_SIZE + tosend));	WARN_ON(!rv); 	if (rv < 0) {		vccdbg("VCC: ldc_write()=%d\n", rv);		vcc_kick_tx(port);	} else {		struct tty_struct *tty = port->tty; 		port->chars_in_buffer = 0;		if (tty)			tty_wakeup(tty);	} done:	spin_unlock_irqrestore(&port->lock, flags);	vcc_put(port, false);} /** * vcc_event() - LDC event processing engine * @arg: VCC private data * @event: LDC event * * Handles LDC events for VCC */static void vcc_event(void *arg, int event){	struct vio_driver_state *vio;	struct vcc_port *port;	unsigned long flags;	int rv; 	port = arg;	vio = &port->vio; 	spin_lock_irqsave(&port->lock, flags); 	switch (event) {	case LDC_EVENT_RESET:	case LDC_EVENT_UP:		vio_link_state_change(vio, event);		break; 	case LDC_EVENT_DATA_READY:		rv = vcc_ldc_read(port);		if (rv == -ECONNRESET)			vio_conn_reset(vio);		break; 	default:		pr_err("VCC: unexpected LDC event(%d)\n", event);	} 	spin_unlock_irqrestore(&port->lock, flags);} static struct ldc_channel_config vcc_ldc_cfg = {	.event		= vcc_event,	.mtu		= VIO_VCC_MTU_SIZE,	.mode		= LDC_MODE_RAW,	.debug		= 0,}; /* Ordered from largest major to lowest */static struct vio_version vcc_versions[] = {	{ .major = 1, .minor = 0 },}; static struct tty_port_operations vcc_port_ops = { 0 }; static ssize_t domain_show(struct device *dev,			   struct device_attribute *attr,			   char *buf){	struct vcc_port *port;	int rv; 	port = dev_get_drvdata(dev);	if (!port)		return -ENODEV; 	rv = scnprintf(buf, PAGE_SIZE, "%s\n", port->domain); 	return rv;} static int vcc_send_ctl(struct vcc_port *port, int ctl){	struct vio_vcc pkt;	int rv; 	pkt.tag.type = VIO_TYPE_CTRL;	pkt.tag.sid = ctl;	pkt.tag.stype = 0;

Lines 1–500 of 1091.