// 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. Load more
// 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;