This change adds support for virtual console server. It adds a module called VCC, or Virtual Console Concentrator. VCC is a VIO driver. It enables access to the guest OS's serial console. It does so by establishing a serial link between the primary OS & guest OS. Signed-off-by: Jagannathan Raman <jag.raman@xxxxxxxxxx> --- arch/sparc/configs/sparc64_defconfig | 1 + arch/sparc/include/asm/vio.h | 9 + arch/sparc/kernel/ldc.c | 2 + arch/sparc/kernel/vio.c | 1 + arch/sparc/kernel/viohs.c | 12 +- drivers/tty/Kconfig | 5 + drivers/tty/Makefile | 1 + drivers/tty/vcc.c | 1193 ++++++++++++++++++++++++++++++++++ 8 files changed, 1221 insertions(+), 3 deletions(-) create mode 100644 drivers/tty/vcc.c diff --git a/arch/sparc/configs/sparc64_defconfig b/arch/sparc/configs/sparc64_defconfig index b2e650d..994967e 100644 --- a/arch/sparc/configs/sparc64_defconfig +++ b/arch/sparc/configs/sparc64_defconfig @@ -242,3 +242,4 @@ CONFIG_CRYPTO_TWOFISH=m # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRC16=m CONFIG_LIBCRC32C=m +CONFIG_VCC=m diff --git a/arch/sparc/include/asm/vio.h b/arch/sparc/include/asm/vio.h index d1c47e9..fca4a91 100644 --- a/arch/sparc/include/asm/vio.h +++ b/arch/sparc/include/asm/vio.h @@ -52,6 +52,7 @@ struct vio_ver_info { #define VDEV_NETWORK_SWITCH 0x02 #define VDEV_DISK 0x03 #define VDEV_DISK_SERVER 0x04 +#define VDEV_CONSOLE_CON 0x05 u8 resv1[3]; u64 resv2[5]; @@ -282,6 +283,14 @@ struct vio_dring_state { struct ldc_trans_cookie cookies[VIO_MAX_RING_COOKIES]; }; +#define VIO_TAG_SIZE (sizeof(struct vio_msg_tag)) +#define VIO_VCC_MTU_SIZE (LDC_PACKET_SIZE - 8) + +struct vio_vcc { + struct vio_msg_tag tag; + char data[VIO_VCC_MTU_SIZE]; +}; + static inline void *vio_dring_cur(struct vio_dring_state *dr) { return dr->base + (dr->entry_size * dr->prod); diff --git a/arch/sparc/kernel/ldc.c b/arch/sparc/kernel/ldc.c index 840e0b2..acffbc8 100644 --- a/arch/sparc/kernel/ldc.c +++ b/arch/sparc/kernel/ldc.c @@ -1480,6 +1480,7 @@ int ldc_rx_reset(struct ldc_channel *lp) { return __set_rx_head(lp, lp->rx_tail); } +EXPORT_SYMBOL(ldc_rx_reset); void __ldc_print(struct ldc_channel *lp, const char *caller) { @@ -1493,6 +1494,7 @@ void __ldc_print(struct ldc_channel *lp, const char *caller) lp->tx_head, lp->tx_tail, lp->tx_num_entries, lp->rcv_nxt, lp->snd_nxt); } +EXPORT_SYMBOL(__ldc_print); static int write_raw(struct ldc_channel *lp, const void *buf, unsigned int size) { diff --git a/arch/sparc/kernel/vio.c b/arch/sparc/kernel/vio.c index c8cf1c2..a0aa6f2 100644 --- a/arch/sparc/kernel/vio.c +++ b/arch/sparc/kernel/vio.c @@ -241,6 +241,7 @@ u64 vio_vdev_node(struct mdesc_handle *hp, struct vio_dev *vdev) return node; } +EXPORT_SYMBOL(vio_vdev_node); static void vio_fill_channel_info(struct mdesc_handle *hp, u64 mp, struct vio_dev *vdev) diff --git a/arch/sparc/kernel/viohs.c b/arch/sparc/kernel/viohs.c index d4f13c0..dcd278f 100644 --- a/arch/sparc/kernel/viohs.c +++ b/arch/sparc/kernel/viohs.c @@ -814,15 +814,21 @@ int vio_driver_init(struct vio_driver_state *vio, struct vio_dev *vdev, case VDEV_NETWORK_SWITCH: case VDEV_DISK: case VDEV_DISK_SERVER: + case VDEV_CONSOLE_CON: break; default: return -EINVAL; } - if (!ops || !ops->send_attr || !ops->handle_attr || - !ops->handshake_complete) - return -EINVAL; + if (dev_class == VDEV_NETWORK || + dev_class == VDEV_NETWORK_SWITCH || + dev_class == VDEV_DISK || + dev_class == VDEV_DISK_SERVER) { + if (!ops || !ops->send_attr || !ops->handle_attr || + !ops->handshake_complete) + return -EINVAL; + } if (!ver_table || ver_table_size < 0) return -EINVAL; diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index 9510305..8643b55 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -455,4 +455,9 @@ config MIPS_EJTAG_FDC_KGDB_CHAN help FDC channel number to use for KGDB. +config VCC + tristate "Sun Virtual Console Concentrator" + depends on SUN_LDOMS + help + Support for Sun logical domain consoles. endif # TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index f02becd..61c065f 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -32,5 +32,6 @@ obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o +obj-$(CONFIG_VCC) += vcc.o obj-y += ipwireless/ diff --git a/drivers/tty/vcc.c b/drivers/tty/vcc.c new file mode 100644 index 0000000..b58bad3 --- /dev/null +++ b/drivers/tty/vcc.c @@ -0,0 +1,1193 @@ +/* + * vcc.c: sun4v virtual channel concentrator + * + * Copyright (C) 2014,2016 Oracle. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/irq.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.0" +#define DRV_MODULE_RELDATE "July 20, 2014" + +static char version[] = + DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; +MODULE_DESCRIPTION("Sun LDOM virtual console concentrator driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_MODULE_VERSION); + +struct vcc { + spinlock_t lock; + char *domain; + struct tty_struct *tty; /* only populated while dev is open */ + u64 port_id; + + u64 refcnt; + bool excl_locked; + + /* + * 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; + struct vio_driver_state vio; +}; + +/* amount of time in ns that thread will delay waiting for a vcc ref */ +#define VCC_REF_DELAY 100 + +#define VCC_MAX_PORTS 256 +#define VCC_MINOR_START 0 +#define VCC_BUFF_LEN VIO_VCC_MTU_SIZE + +#define VCC_CTL_BREAK -1 +#define VCC_CTL_HUP -2 + +#define TIMER_SET(v, x, t) ((v)->x##_timer.expires = (t)) +#define TIMER_CLEAR(v, x) ((v)->x##_timer.expires = 0) +#define TIMER_ACTIVE(v, x) ((v)->x##_timer.expires) + +static const char vcc_driver_name[] = "vcc"; +static const char vcc_device_node[] = "vcc"; +static struct tty_driver *vcc_tty_driver; + +static struct vcc *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 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 +}; + +static void vcc_table_add(struct vcc *vcc) +{ + unsigned long flags; + + spin_lock_irqsave(&vcc_table_lock, flags); + vcc_table[vcc->port_id] = vcc; + spin_unlock_irqrestore(&vcc_table_lock, flags); +} + +static void vcc_table_remove(u64 port_id) +{ + unsigned long flags; + + BUG_ON(port_id >= VCC_MAX_PORTS); + + spin_lock_irqsave(&vcc_table_lock, flags); + vcc_table[port_id] = NULL; + spin_unlock_irqrestore(&vcc_table_lock, flags); +} + +static struct vcc *vcc_get(u64 port_id, bool excl) +{ + unsigned long flags; + struct vcc *vcc; + +try_again: + + spin_lock_irqsave(&vcc_table_lock, flags); + + vcc = vcc_table[port_id]; + if (!vcc) { + spin_unlock_irqrestore(&vcc_table_lock, flags); + return NULL; + } + + if (!excl) { + if (vcc->excl_locked) { + spin_unlock_irqrestore(&vcc_table_lock, flags); + udelay(VCC_REF_DELAY); + goto try_again; + } + vcc->refcnt++; + spin_unlock_irqrestore(&vcc_table_lock, flags); + return vcc; + } + + if (vcc->refcnt) { + spin_unlock_irqrestore(&vcc_table_lock, flags); + /* + * Threads wanting exclusive access to a + * vcc will wait half the time - which should give + * them higher priority in the case of multiple + * waiters. + */ + udelay(VCC_REF_DELAY/2); + goto try_again; + } + + vcc->refcnt++; + vcc->excl_locked = true; + spin_unlock_irqrestore(&vcc_table_lock, flags); + + return vcc; +} + +static void vcc_put(struct vcc *vcc, bool excl) +{ + unsigned long flags; + + if (!vcc) + return; + + spin_lock_irqsave(&vcc_table_lock, flags); + + /* check if caller attempted to put with the wrong flags */ + BUG_ON((excl && !vcc->excl_locked) || (!excl && vcc->excl_locked)); + + vcc->refcnt--; + + if (excl) + vcc->excl_locked = false; + + spin_unlock_irqrestore(&vcc_table_lock, flags); +} + +static void vcc_kick_rx(struct vcc *vcc) +{ + struct vio_driver_state *vio = &vcc->vio; + + vccdbg("%s\n", __func__); + + assert_spin_locked(&vcc->lock); + + if (TIMER_ACTIVE(vcc, rx)) + return; + + /* + * Disable interrupts until we can read the data again. + */ + if (vio->vdev->rx_irq) + disable_irq(vio->vdev->rx_irq); + + TIMER_SET(vcc, rx, jiffies + 1); + add_timer(&vcc->rx_timer); +} + +static void vcc_kick_tx(struct vcc *vcc) +{ + vccdbg("%s\n", __func__); + + assert_spin_locked(&vcc->lock); + + if (TIMER_ACTIVE(vcc, tx)) + return; + + TIMER_SET(vcc, tx, jiffies + 1); + add_timer(&vcc->tx_timer); +} + +static int vcc_rx_check(struct tty_struct *tty, int size) +{ + BUG_ON(!tty || !tty->port); + + /* + * tty_buffer_request_room eventually calls kmalloc with GFP_ATOMIC + * so it won't sleep. + */ + 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; + + BUG_ON(!tty || !tty->port); + + /* + * tty_insert_flig_string... calls __tty_buffer_request_room. + */ + len = tty_insert_flip_string(tty->port, buf, size); + + /* This is synch because tty->low_latency == 1 */ + if (len) + tty_flip_buffer_push(tty->port); + + vccdbg("%s: rv=%d\n", __func__, len); + + return len; +} + +static int vcc_ldc_read(struct vcc *vcc) +{ + struct vio_driver_state *vio = &vcc->vio; + struct tty_struct *tty; + struct vio_vcc pkt; + int rv = 0; + vccdbg("%s\n", __func__); + + tty = vcc->tty; + if (!tty) { + rv = ldc_rx_reset(vio->lp); /* TODO - is this needed? */ + vccdbg("%s: reset rx q: rv=%d\n", __func__, rv); + goto done; + } + + /* + * Read as long as the LDC has incoming data. + * TODO Since we read in interrupt context, should we defer to + * a lower IRQ level? + */ + while (1) { + if (!vcc_rx_check(tty, VIO_VCC_MTU_SIZE)) { + vcc_kick_rx(vcc); + break; + } + vccdbgl(vio->lp); + rv = ldc_read(vio->lp, &pkt, sizeof(pkt)); + if (rv <= 0) + break; + + vccdbg("%s: ldc_read()=%d\n", __func__, 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) { + /* + * We called vcc_rx_check before which should allocate + * space so this should not fail. + */ + vccdbgp(pkt); + vcc_rx(tty, pkt.data, pkt.tag.stype); + } else { + pr_err("%s: unknown msg [%02x:%02x:%04x:%08x]\n", + __func__, pkt.tag.type, pkt.tag.stype, + pkt.tag.stype_env, pkt.tag.sid); + + rv = -ECONNRESET; + break; + } + BUG_ON(rv != LDC_PACKET_SIZE); + } +done: + vccdbg("%s: rv=%d\n", __func__, rv); + return rv; +} + +static void vcc_rx_timer(unsigned long arg) +{ + struct vcc *vcc; + struct vio_driver_state *vio; + unsigned long flags; + int rv; + + vccdbg("%s\n", __func__); + + vcc = vcc_get((u64)arg, false); + + /* if the device was removed do nothing */ + if (!vcc) + return; + + spin_lock_irqsave(&vcc->lock, flags); + TIMER_CLEAR(vcc, rx); + + vio = &vcc->vio; + + /* + * Re-enable interrupts. + */ + if (vcc->vio.vdev->rx_irq) + enable_irq(vcc->vio.vdev->rx_irq); + + /* if the device was closed do nothing */ + if (!vcc->tty) { + /* + * TODO - If we have have outstanding LDC read data, + * it may be a good idea to flush the LDC read queue + * here to prevent the LDC from re-issuing the LDC + * data event over and over again. (TBD) + */ + spin_unlock_irqrestore(&vcc->lock, flags); + vcc_put(vcc, false); + return; + } + + rv = vcc_ldc_read(vcc); + if (rv == -ECONNRESET) + vio_conn_reset(vio); /* currently a noop */ + + spin_unlock_irqrestore(&vcc->lock, flags); + + vcc_put(vcc, false); + + vccdbg("%s done\n", __func__); +} + +static void vcc_tx_timer(unsigned long arg) +{ + struct vcc *vcc; + struct vio_vcc *pkt; + unsigned long flags; + int tosend = 0; + int rv; + + vccdbg("%s\n", __func__); + + vcc = vcc_get((u64)arg, false); + + /* if the device was removed do nothing */ + if (!vcc) + return; + + spin_lock_irqsave(&vcc->lock, flags); + TIMER_CLEAR(vcc, tx); + + /* if the device was closed do nothing */ + if (!vcc->tty) { + spin_unlock_irqrestore(&vcc->lock, flags); + vcc_put(vcc, false); + return; + } + + tosend = min(VCC_BUFF_LEN, vcc->chars_in_buffer); + if (!tosend) + goto done; + + pkt = &vcc->buffer; + pkt->tag.type = VIO_TYPE_DATA; + pkt->tag.stype = tosend; + vccdbgl(vcc->vio.lp); + + /* won't send partial writes */ + rv = ldc_write(vcc->vio.lp, pkt, VIO_TAG_SIZE + tosend); + BUG_ON(!rv); + + if (rv < 0) { + vccdbg("%s: ldc_write()=%d\n", __func__, rv); + vcc_kick_tx(vcc); + } else { + struct tty_struct *tty = vcc->tty; + + vcc->chars_in_buffer = 0; + + /* + * We are still obligated to deliver the data to the + * hypervisor even if the tty has been closed because + * we committed to delivering it. But don't try to wake + * a non-existent tty. + */ + if (tty) + tty_wakeup(tty); + } +done: + spin_unlock_irqrestore(&vcc->lock, flags); + + vcc_put(vcc, false); + + vccdbg("%s done\n", __func__); +} + +static void vcc_event(void *arg, int event) +{ + struct vcc *vcc = arg; + struct vio_driver_state *vio = &vcc->vio; + unsigned long flags; + int rv; + + vccdbg("%s(%d)\n", __func__, event); + spin_lock_irqsave(&vcc->lock, flags); + + if (event == LDC_EVENT_RESET || event == LDC_EVENT_UP) { + vio_link_state_change(vio, event); + spin_unlock_irqrestore(&vcc->lock, flags); + return; + } + + if (event != LDC_EVENT_DATA_READY) { + pr_err("%s: unexpected LDC event %d\n", __func__, event); + spin_unlock_irqrestore(&vcc->lock, flags); + return; + } + + rv = vcc_ldc_read(vcc); + if (rv == -ECONNRESET) + vio_conn_reset(vio); /* currently a noop */ + + spin_unlock_irqrestore(&vcc->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 vcc_sysfs_domain_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + int rv; + struct vcc *vcc; + + vcc = dev_get_drvdata(device); + if (!vcc) + return -ENODEV; + + rv = scnprintf(buf, PAGE_SIZE, "%s\n", vcc->domain); + + return rv; +} + +static int vcc_send_ctl(struct vcc *vcc, int ctl) +{ + int rv; + struct vio_vcc pkt; + + pkt.tag.type = VIO_TYPE_CTRL; + pkt.tag.sid = ctl; /* ctrl_msg */ + pkt.tag.stype = 0; /* size */ + + rv = ldc_write(vcc->vio.lp, &pkt, sizeof(pkt.tag)); + BUG_ON(!rv); + vccdbg("%s: ldc_write(%ld)=%d\n", __func__, sizeof(pkt.tag), rv); + + return rv; +} + +static ssize_t vcc_sysfs_break_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rv = count; + int brk; + unsigned long flags; + struct vcc *vcc; + + vcc = dev_get_drvdata(device); + if (!vcc) + return -ENODEV; + + spin_lock_irqsave(&vcc->lock, flags); + + if (sscanf(buf, "%ud", &brk) != 1 || brk != 1) + rv = -EINVAL; + else if (vcc_send_ctl(vcc, VCC_CTL_BREAK) < 0) + vcc_kick_tx(vcc); + + spin_unlock_irqrestore(&vcc->lock, flags); + + return count; +} + +static DEVICE_ATTR(domain, S_IRUSR, vcc_sysfs_domain_show, NULL); +static DEVICE_ATTR(break, S_IWUSR, NULL, vcc_sysfs_break_store); + +static struct attribute *vcc_sysfs_entries[] = { + &dev_attr_domain.attr, + &dev_attr_break.attr, + NULL +}; + +static struct attribute_group vcc_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = vcc_sysfs_entries, +}; + +static void print_version(void) +{ + printk_once(KERN_INFO "%s", version); +} + +static int vcc_probe(struct vio_dev *vdev, + const struct vio_device_id *id) +{ + int rv; + char *name; + const char *domain; + struct vcc *vcc; + struct device *dev; + struct mdesc_handle *hp; + u64 node; + + print_version(); + + vccdbg("%s: name=%s port=%ld\n", __func__, dev_name(&vdev->dev), + vdev->port_id); + + if (vdev->port_id >= VCC_MAX_PORTS) + return -ENXIO; + + if (!vcc_tty_driver) { + pr_err("%s: vcc tty driver not registered\n", __func__); + return -ENODEV; + } + + vcc = kzalloc(sizeof(*vcc), GFP_KERNEL); + if (!vcc) { + pr_err("%s: cannot allocate vcc\n", __func__); + return -ENOMEM; + } + + name = kstrdup(dev_name(&vdev->dev), GFP_KERNEL); + rv = vio_driver_init(&vcc->vio, vdev, VDEV_CONSOLE_CON, + vcc_versions, ARRAY_SIZE(vcc_versions), + NULL, name); + if (rv) + goto free_vcc; + + vcc->vio.debug = vcc_dbg_vio; + vcc_ldc_cfg.debug = vcc_dbg_ldc; + + rv = vio_ldc_alloc(&vcc->vio, &vcc_ldc_cfg, vcc); + if (rv) + goto free_vcc; + + spin_lock_init(&vcc->lock); + vcc->port_id = vdev->port_id; + vcc_table_add(vcc); + + /* + * Register the device using the port ID as the index. + * TODO - Device registration should probably be done last + * since the device is "live" as soon as it's called (and + * calls into the tty/vcc infrastructure can start + * happening for this device immediately afterwards). + */ + dev = tty_register_device(vcc_tty_driver, vdev->port_id, &vdev->dev); + if (IS_ERR(dev)) { + rv = PTR_ERR(dev); + goto free_ldc; + } + + hp = mdesc_grab(); + + node = vio_vdev_node(hp, vdev); + if (node == MDESC_NODE_NULL) { + rv = -ENXIO; + mdesc_release(hp); + goto unreg_tty; + } + + domain = mdesc_get_property(hp, node, "vcc-domain-name", NULL); + if (!domain) { + rv = -ENXIO; + mdesc_release(hp); + goto unreg_tty; + } + vcc->domain = kstrdup(domain, GFP_KERNEL); + + mdesc_release(hp); + + rv = sysfs_create_group(&vdev->dev.kobj, &vcc_attribute_group); + if (rv) + goto free_domain; + + init_timer(&vcc->rx_timer); + vcc->rx_timer.function = vcc_rx_timer; + vcc->rx_timer.data = (unsigned long)vdev->port_id; + + init_timer(&vcc->tx_timer); + vcc->tx_timer.function = vcc_tx_timer; + vcc->tx_timer.data = (unsigned long)vdev->port_id; + + dev_set_drvdata(&vdev->dev, vcc); + + /* + * Disable interrupts before the port is up. + * + * We can get an interrupt during vio_port_up() -> ldc_bind(). + * vio_port_up() grabs the vio->lock beforehand so we cannot + * grab it in vcc_event(). + * + * Once the port is up and the lock released, we can field + * interrupts. + */ + if (vcc->vio.vdev->rx_irq) + disable_irq(vcc->vio.vdev->rx_irq); + + vio_port_up(&vcc->vio); + + if (vcc->vio.vdev->rx_irq) + enable_irq(vcc->vio.vdev->rx_irq); + + return 0; + +free_domain: + kfree(vcc->domain); + +unreg_tty: + tty_unregister_device(vcc_tty_driver, vdev->port_id); + +free_ldc: + vcc_table_remove(vdev->port_id); + vio_ldc_free(&vcc->vio); + +free_vcc: + kfree(name); + kfree(vcc); + + return rv; +} + +static int vcc_remove(struct vio_dev *vdev) +{ + struct vcc *vcc = dev_get_drvdata(&vdev->dev); + struct tty_struct *tty; + + vccdbg("%s\n", __func__); + + if (!vcc) + return -ENODEV; + + del_timer_sync(&vcc->rx_timer); + del_timer_sync(&vcc->tx_timer); + + /* + * If there's a process with the device open, + * hangup the tty. + */ + tty = vcc->tty; + if (tty) + tty_vhangup(tty); + + /* Get an exclusive ref to the vcc */ + vcc = vcc_get(vdev->port_id, true); + + BUG_ON(!vcc); + + tty_unregister_device(vcc_tty_driver, vdev->port_id); + + del_timer_sync(&vcc->vio.timer); + vio_ldc_free(&vcc->vio); + sysfs_remove_group(&vdev->dev.kobj, &vcc_attribute_group); + dev_set_drvdata(&vdev->dev, NULL); + + vcc_table_remove(vdev->port_id); + + kfree(vcc->vio.name); + kfree(vcc->domain); + kfree(vcc); + + /* + * Since the vcc has been freed and removed from + * the table, no need to call vcc_put() here. Any threads + * waiting on vcc_get() will notice the vcc removed + * from the list and unblock with a NULL returned for + * the vcc. Since we grabbed exclusive + * access to the vcc, we are guaranteed that no other + * access to the vcc will be made - including any puts. + */ + + return 0; +} + +static const struct vio_device_id vcc_match[] = { + { + .type = "vcc-port", + }, + {}, +}; +MODULE_DEVICE_TABLE(vio, vcc_match); + +static struct vio_driver vcc_driver = { + .id_table = vcc_match, + .probe = vcc_probe, + .remove = vcc_remove, + .name = "vcc", +}; + +static int vcc_open(struct tty_struct *tty, struct file *filp) +{ + struct vcc *vcc; + struct tty_port *port; + int rv; + + vccdbg("%s\n", __func__); + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return -ENXIO; + } + + /* Only allow a single open */ + if (tty->count > 1) + return -EBUSY; + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return -ENXIO; + } + + if (!vcc->vio.lp) { + pr_err("%s: NULL lp\n", __func__); + vcc_put(vcc, false); + return -ENXIO; + } + vccdbgl(vcc->vio.lp); + + vcc_put(vcc, false); + + port = tty->port; + if (!port) { + pr_err("%s: NULL tty port\n", __func__); + return -ENXIO; + } + + if (!port->ops) { + pr_err("%s: NULL tty port ops\n", __func__); + return -ENXIO; + } + + rv = tty_port_open(port, tty, filp); + + return rv; +} + +static void vcc_close(struct tty_struct *tty, struct file *filp) +{ + vccdbg("%s\n", __func__); + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return; + } + + /* if this isn't the last close just return */ + if (tty->count > 1) + return; + + if (!tty->port) { + pr_err("%s: NULL tty port\n", __func__); + return; + } + + tty_port_close(tty->port, tty, filp); +} + +static void vcc_ldc_hup(struct vcc *vcc) +{ + unsigned long flags; + + vccdbg("%s\n", __func__); + + spin_lock_irqsave(&vcc->lock, flags); + + if (vcc_send_ctl(vcc, VCC_CTL_HUP) < 0) + vcc_kick_tx(vcc); + + spin_unlock_irqrestore(&vcc->lock, flags); +} + +static void vcc_hangup(struct tty_struct *tty) +{ + struct vcc *vcc; + struct tty_port *port; + + vccdbg("%s\n", __func__); + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return; + } + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return; + } + + port = tty->port; + if (!port) { + pr_err("%s: NULL tty port\n", __func__); + vcc_put(vcc, false); + return; + } + + vcc_ldc_hup(vcc); + + vcc_put(vcc, false); + + tty_port_hangup(port); +} + +static int vcc_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct vcc *vcc; + struct vio_vcc *pkt; + unsigned long flags; + int total_sent = 0; + int tosend = 0; + int rv = -EINVAL; + + vccdbg("%s\n", __func__); + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return -ENXIO; + } + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return -ENXIO; + } + + spin_lock_irqsave(&vcc->lock, flags); + + pkt = &vcc->buffer; + pkt->tag.type = VIO_TYPE_DATA; + + while (count > 0) { + tosend = min(count, (VCC_BUFF_LEN - vcc->chars_in_buffer)); + /* + * No more space, this probably means that the last call to + * vcc_write() didn't succeed and the buffer was filled up. + */ + if (!tosend) + break; + + memcpy(&pkt->data[vcc->chars_in_buffer], + &buf[total_sent], + tosend); + + vcc->chars_in_buffer += tosend; + + pkt->tag.stype = tosend; + vccdbg("TAG [%02x:%02x:%04x:%08x]\n", + pkt->tag.type, + pkt->tag.stype, + pkt->tag.stype_env, + pkt->tag.sid); + vccdbg("DATA [%s]\n", pkt->data); + vccdbgl(vcc->vio.lp); + + /* won't send partial writes */ + rv = ldc_write(vcc->vio.lp, pkt, VIO_TAG_SIZE + tosend); + vccdbg("%s: ldc_write(%ld)=%d\n", __func__, + VIO_TAG_SIZE + tosend, rv); + + /* + * Since we know we have enough room in vcc->buffer for + * tosend we record that it was sent regardless of whether the + * hypervisor actually took it because we have it buffered. + */ + total_sent += tosend; + count -= tosend; + if (rv < 0) { + vcc_kick_tx(vcc); + break; + } + + vcc->chars_in_buffer = 0; + } + + spin_unlock_irqrestore(&vcc->lock, flags); + + vcc_put(vcc, false); + + vccdbg("%s: total=%d rv=%d\n", __func__, total_sent, rv); + + return total_sent ? total_sent : rv; +} + +static int vcc_write_room(struct tty_struct *tty) +{ + struct vcc *vcc; + u64 num; + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return -ENXIO; + } + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return -ENXIO; + } + + num = VCC_BUFF_LEN - vcc->chars_in_buffer; + + vcc_put(vcc, false); + + return num; +} + +static int vcc_chars_in_buffer(struct tty_struct *tty) +{ + struct vcc *vcc; + u64 num; + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return -ENXIO; + } + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return -ENXIO; + } + + num = vcc->chars_in_buffer; + + vcc_put(vcc, false); + + return num; +} + +static int vcc_break_ctl(struct tty_struct *tty, int state) +{ + struct vcc *vcc; + unsigned long flags; + + vccdbg("%s(%d)\n", __func__, state); + + if (!tty) { + pr_err("%s: NULL tty\n", __func__); + return -ENXIO; + } + + vcc = vcc_get(tty->index, false); + if (!vcc) { + pr_err("%s: NULL vcc\n", __func__); + return -ENXIO; + } + + if (state == 0) { /* turn off break */ + vcc_put(vcc, false); + return 0; + } + + spin_lock_irqsave(&vcc->lock, flags); + + if (vcc_send_ctl(vcc, VCC_CTL_BREAK) < 0) + vcc_kick_tx(vcc); + + spin_unlock_irqrestore(&vcc->lock, flags); + + vcc_put(vcc, false); + + return 0; +} + +static int vcc_install(struct tty_driver *driver, struct tty_struct *tty) +{ + int ret; + struct vcc *vcc; + struct tty_port *port; + + if (tty->index >= VCC_MAX_PORTS) + return -EINVAL; + + ret = tty_standard_install(driver, tty); + if (ret) + return ret; + + /* alloc and assign a port for the tty */ + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + pr_err("%s: cannot allocate tty_port\n", __func__); + return -ENOMEM; + } + tty_port_init(port); + port->ops = &vcc_port_ops; + tty->port = port; + + vcc = vcc_get(tty->index, true); + if (!vcc) { + tty->port = NULL; + kfree(port); + return -ENODEV; + } + + /* assign the tty to the vcc */ + vcc->tty = tty; + + vcc_put(vcc, true); + + return 0; +} + +static void vcc_cleanup(struct tty_struct *tty) +{ + struct vcc *vcc; + + vcc = vcc_get(tty->index, true); + if (vcc) { + vcc->tty = NULL; + vcc_put(vcc, true); + } + + tty_port_destroy(tty->port); + kfree(tty->port); + tty->port = NULL; +} + +static const struct tty_operations vcc_ops = { + .open = vcc_open, + .close = vcc_close, + .hangup = vcc_hangup, + .write = vcc_write, + .write_room = vcc_write_room, + .chars_in_buffer = vcc_chars_in_buffer, + .break_ctl = vcc_break_ctl, + .install = vcc_install, + .cleanup = vcc_cleanup +}; + +/* + * We want to dynamically manage our ports through the tty_port_* + * interfaces so we allocate and register/unregister on our own. + */ +#define VCC_TTY_FLAGS (TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_REAL_RAW) + +static int vcc_tty_init(void) +{ + int rv; + + vcc_tty_driver = tty_alloc_driver(VCC_MAX_PORTS, VCC_TTY_FLAGS); + + if (!vcc_tty_driver) { + pr_err("%s: tty driver alloc failed\n", __func__); + return -ENOMEM; + } + + vcc_tty_driver->driver_name = vcc_driver_name; + vcc_tty_driver->name = vcc_device_node; + + /* + * We'll let the system assign us a major number, indicated by leaving + * it blank. + */ + vcc_tty_driver->minor_start = VCC_MINOR_START; + vcc_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM; + vcc_tty_driver->init_termios = vcc_tty_termios; + + tty_set_operations(vcc_tty_driver, &vcc_ops); + + /* + * The following call will result in sysfs entries that denote the + * dynamically assigned major and minor numbers for our devices. + */ + rv = tty_register_driver(vcc_tty_driver); + if (!rv) { + vccdbg("%s: tty driver registered\n", __func__); + return 0; + } + + pr_err("%s: tty driver register failed\n", __func__); + + put_tty_driver(vcc_tty_driver); + vcc_tty_driver = NULL; + + return rv; +} + +static void vcc_tty_exit(void) +{ + vccdbg("%s\n", __func__); + + tty_unregister_driver(vcc_tty_driver); + put_tty_driver(vcc_tty_driver); + vccdbg("%s: tty driver unregistered\n", __func__); + + vcc_tty_driver = NULL; +} + +static int __init vcc_init(void) +{ + int rv; + + vccdbg("%s\n", __func__); + + rv = vcc_tty_init(); + if (rv) { + pr_err("%s: vcc_tty_init failed (%d)\n", __func__, rv); + return rv; + } + + rv = vio_register_driver(&vcc_driver); + if (rv) { + pr_err("%s: vcc driver register failed (%d)\n", __func__, rv); + vcc_tty_exit(); + } else { + vccdbg("%s: vcc driver registered\n", __func__); + } + + return rv; +} + +static void __exit vcc_exit(void) +{ + vccdbg("%s\n", __func__); + vio_unregister_driver(&vcc_driver); + vccdbg("%s: vcc vio driver unregistered\n", __func__); + vcc_tty_exit(); + vccdbg("%s: vcc tty driver unregistered\n", __func__); +} + +module_init(vcc_init); +module_exit(vcc_exit); -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe sparclinux" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html