Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092// 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;
rv = ldc_write(port->vio.lp, &pkt, sizeof(pkt.tag)); WARN_ON(!rv); vccdbg("VCC: ldc_write(%ld)=%d\n", sizeof(pkt.tag), rv);
return rv;}
static ssize_t break_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct vcc_port *port; unsigned long flags; int rv = count; int brk;
port = dev_get_drvdata(dev); if (!port) return -ENODEV;
spin_lock_irqsave(&port->lock, flags);
if (sscanf(buf, "%ud", &brk) != 1 || brk != 1) rv = -EINVAL; else if (vcc_send_ctl(port, VCC_CTL_BREAK) < 0) vcc_kick_tx(port);
spin_unlock_irqrestore(&port->lock, flags);
return rv;}
static DEVICE_ATTR_ADMIN_RO(domain);static DEVICE_ATTR_WO(break);
static struct attribute *vcc_sysfs_entries[] = { &dev_attr_domain.attr, &dev_attr_break.attr, NULL};
static struct attribute_group vcc_attribute_group = { .name = NULL, .attrs = vcc_sysfs_entries,};
/** * vcc_probe() - Initialize VCC port * @vdev: Pointer to VIO device of the new VCC port * @id: VIO device ID * * Initializes a VCC port to receive serial console data from * the guest domain. Sets up a TTY end point on the control * domain. Sets up VIO/LDC link between the guest & control * domain endpoints. * * Return: status of the probe */static int vcc_probe(struct vio_dev *vdev, const struct vio_device_id *id){ struct mdesc_handle *hp; struct vcc_port *port; struct device *dev; const char *domain; char *name; u64 node; int rv;
vccdbg("VCC: name=%s\n", dev_name(&vdev->dev));
if (!vcc_tty_driver) { pr_err("VCC: TTY driver not registered\n"); return -ENODEV; }
port = kzalloc_obj(struct vcc_port); if (!port) return -ENOMEM;
name = kstrdup(dev_name(&vdev->dev), GFP_KERNEL); if (!name) { rv = -ENOMEM; goto free_port; }
rv = vio_driver_init(&port->vio, vdev, VDEV_CONSOLE_CON, vcc_versions, ARRAY_SIZE(vcc_versions), NULL, name); if (rv) goto free_name;
port->vio.debug = vcc_dbg_vio; vcc_ldc_cfg.debug = vcc_dbg_ldc;
rv = vio_ldc_alloc(&port->vio, &vcc_ldc_cfg, port); if (rv) goto free_name;
spin_lock_init(&port->lock);
port->index = vcc_table_add(port); if (port->index == -1) { pr_err("VCC: no more TTY indices left for allocation\n"); rv = -ENOMEM; goto free_ldc; }
/* Register the device using VCC table index as TTY index */ dev = tty_register_device(vcc_tty_driver, port->index, &vdev->dev); if (IS_ERR(dev)) { rv = PTR_ERR(dev); goto free_table; }
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; } port->domain = kstrdup(domain, GFP_KERNEL); if (!port->domain) { rv = -ENOMEM; goto unreg_tty; }
mdesc_release(hp);
rv = sysfs_create_group(&vdev->dev.kobj, &vcc_attribute_group); if (rv) goto free_domain;
timer_setup(&port->rx_timer, vcc_rx_timer, 0); timer_setup(&port->tx_timer, vcc_tx_timer, 0);
dev_set_drvdata(&vdev->dev, port);
/* It's possible to receive IRQs in the middle of vio_port_up. Disable * IRQs until the port is up. */ disable_irq_nosync(vdev->rx_irq); vio_port_up(&port->vio); enable_irq(vdev->rx_irq);
return 0;
free_domain: kfree(port->domain);unreg_tty: tty_unregister_device(vcc_tty_driver, port->index);free_table: vcc_table_remove(port->index);free_ldc: vio_ldc_free(&port->vio);free_name: kfree(name);free_port: kfree(port);
return rv;}
/** * vcc_remove() - Terminate a VCC port * @vdev: Pointer to VIO device of the VCC port * * Terminates a VCC port. Sets up the teardown of TTY and * VIO/LDC link between guest and primary domains. * * Return: status of removal */static void vcc_remove(struct vio_dev *vdev){ struct vcc_port *port = dev_get_drvdata(&vdev->dev);
timer_delete_sync(&port->rx_timer); timer_delete_sync(&port->tx_timer);
/* If there's a process with the device open, do a synchronous * hangup of the TTY. This *may* cause the process to call close * asynchronously, but it's not guaranteed. */ if (port->tty) tty_vhangup(port->tty);
/* Get exclusive reference to VCC, ensures that there are no other * clients to this port. This cannot fail. */ vcc_get(port->index, true);
tty_unregister_device(vcc_tty_driver, port->index);
timer_delete_sync(&port->vio.timer); vio_ldc_free(&port->vio); sysfs_remove_group(&vdev->dev.kobj, &vcc_attribute_group); dev_set_drvdata(&vdev->dev, NULL); if (port->tty) { port->removed = true; vcc_put(port, true); } else { vcc_table_remove(port->index);
kfree(port->vio.name); kfree(port->domain); kfree(port); }}
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 *vcc_file){ struct vcc_port *port;
if (tty->count > 1) return -EBUSY;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: open: Failed to find VCC port\n"); return -ENODEV; }
if (unlikely(!port->vio.lp)) { pr_err("VCC: open: LDC channel not configured\n"); vcc_put(port, false); return -EPIPE; } vccdbgl(port->vio.lp);
vcc_put(port, false);
if (unlikely(!tty->port)) { pr_err("VCC: open: TTY port not found\n"); return -ENXIO; }
if (unlikely(!tty->port->ops)) { pr_err("VCC: open: TTY ops not defined\n"); return -ENXIO; }
return tty_port_open(tty->port, tty, vcc_file);}
static void vcc_close(struct tty_struct *tty, struct file *vcc_file){ if (unlikely(tty->count > 1)) return;
if (unlikely(!tty->port)) { pr_err("VCC: close: TTY port not found\n"); return; }
tty_port_close(tty->port, tty, vcc_file);}
static void vcc_ldc_hup(struct vcc_port *port){ unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
if (vcc_send_ctl(port, VCC_CTL_HUP) < 0) vcc_kick_tx(port);
spin_unlock_irqrestore(&port->lock, flags);}
static void vcc_hangup(struct tty_struct *tty){ struct vcc_port *port;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: hangup: Failed to find VCC port\n"); return; }
if (unlikely(!tty->port)) { pr_err("VCC: hangup: TTY port not found\n"); vcc_put(port, false); return; }
vcc_ldc_hup(port);
vcc_put(port, false);
tty_port_hangup(tty->port);}
static ssize_t vcc_write(struct tty_struct *tty, const u8 *buf, size_t count){ struct vcc_port *port; struct vio_vcc *pkt; unsigned long flags; size_t total_sent = 0; size_t tosend = 0; int rv = -EINVAL;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: write: Failed to find VCC port"); return -ENODEV; }
spin_lock_irqsave(&port->lock, flags);
pkt = &port->buffer; pkt->tag.type = VIO_TYPE_DATA;
while (count > 0) { /* Minimum of data to write and space available */ tosend = min_t(size_t, count, (VCC_BUFF_LEN - port->chars_in_buffer));
if (!tosend) break;
memcpy(&pkt->data[port->chars_in_buffer], &buf[total_sent], tosend); port->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(port->vio.lp);
/* 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. */ rv = ldc_write(port->vio.lp, pkt, (VIO_TAG_SIZE + tosend)); vccdbg("VCC: write: ldc_write(%zu)=%d\n", (VIO_TAG_SIZE + tosend), rv);
total_sent += tosend; count -= tosend; if (rv < 0) { vcc_kick_tx(port); break; }
port->chars_in_buffer = 0; }
spin_unlock_irqrestore(&port->lock, flags);
vcc_put(port, false);
vccdbg("VCC: write: total=%zu rv=%d", total_sent, rv);
return total_sent ? total_sent : rv;}
static unsigned int vcc_write_room(struct tty_struct *tty){ struct vcc_port *port; unsigned int num;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: write_room: Failed to find VCC port\n"); return 0; }
num = VCC_BUFF_LEN - port->chars_in_buffer;
vcc_put(port, false);
return num;}
static unsigned int vcc_chars_in_buffer(struct tty_struct *tty){ struct vcc_port *port; unsigned int num;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: chars_in_buffer: Failed to find VCC port\n"); return 0; }
num = port->chars_in_buffer;
vcc_put(port, false);
return num;}
static int vcc_break_ctl(struct tty_struct *tty, int state){ struct vcc_port *port; unsigned long flags;
port = vcc_get_ne(tty->index); if (unlikely(!port)) { pr_err("VCC: break_ctl: Failed to find VCC port\n"); return -ENODEV; }
/* Turn off break */ if (state == 0) { vcc_put(port, false); return 0; }
spin_lock_irqsave(&port->lock, flags);
if (vcc_send_ctl(port, VCC_CTL_BREAK) < 0) vcc_kick_tx(port);
spin_unlock_irqrestore(&port->lock, flags);
vcc_put(port, false);
return 0;}
static int vcc_install(struct tty_driver *driver, struct tty_struct *tty){ struct vcc_port *port_vcc; struct tty_port *port_tty; int ret;
if (tty->index >= VCC_MAX_PORTS) return -EINVAL;
ret = tty_standard_install(driver, tty); if (ret) return ret;
port_tty = kzalloc_obj(struct tty_port); if (!port_tty) return -ENOMEM;
port_vcc = vcc_get(tty->index, true); if (!port_vcc) { pr_err("VCC: install: Failed to find VCC port\n"); tty->port = NULL; kfree(port_tty); return -ENODEV; }
tty_port_init(port_tty); port_tty->ops = &vcc_port_ops; tty->port = port_tty;
port_vcc->tty = tty;
vcc_put(port_vcc, true);
return 0;}
static void vcc_cleanup(struct tty_struct *tty){ struct vcc_port *port;
port = vcc_get(tty->index, true); if (port) { port->tty = NULL;
if (port->removed) { vcc_table_remove(tty->index); kfree(port->vio.name); kfree(port->domain); kfree(port); } else { vcc_put(port, 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,};
#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 (IS_ERR(vcc_tty_driver)) { pr_err("VCC: TTY driver alloc failed\n"); return PTR_ERR(vcc_tty_driver); }
vcc_tty_driver->driver_name = "vcc"; vcc_tty_driver->name = "vcc";
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);
rv = tty_register_driver(vcc_tty_driver); if (rv) { pr_err("VCC: TTY driver registration failed\n"); tty_driver_kref_put(vcc_tty_driver); vcc_tty_driver = NULL; return rv; }
vccdbg("VCC: TTY driver registered\n");
return 0;}
static void vcc_tty_exit(void){ tty_unregister_driver(vcc_tty_driver); tty_driver_kref_put(vcc_tty_driver); vccdbg("VCC: TTY driver unregistered\n");
vcc_tty_driver = NULL;}
static int __init vcc_init(void){ int rv;
rv = vcc_tty_init(); if (rv) { pr_err("VCC: TTY init failed\n"); return rv; }
rv = vio_register_driver(&vcc_driver); if (rv) { pr_err("VCC: VIO driver registration failed\n"); vcc_tty_exit(); } else { vccdbg("VCC: VIO driver registered successfully\n"); }
return rv;}
static void __exit vcc_exit(void){ vio_unregister_driver(&vcc_driver); vccdbg("VCC: VIO driver unregistered\n"); vcc_tty_exit(); vccdbg("VCC: TTY driver unregistered\n");}
module_init(vcc_init);module_exit(vcc_exit);