* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include <linux/cdev.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
#include <linux/list.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_console.h>
+#include <linux/wait.h>
#include <linux/workqueue.h>
#include "hvc_console.h"
* across multiple devices and multiple ports per device.
*/
struct ports_driver_data {
+ /* Used for registering chardevs */
+ struct class *class;
+
+ /* Used for exporting per-port information to debugfs */
+ struct dentry *debugfs_dir;
+
+ /* Number of devices this driver is handling */
+ unsigned int index;
+
/*
* This is used to keep track of the number of hvc consoles
* spawned by this driver. This number is given as the first
* notification
*/
struct work_struct control_work;
+ struct work_struct config_work;
struct list_head ports;
/* Array of per-port IO virtqueues */
struct virtqueue **in_vqs, **out_vqs;
+
+ /* Used for numbering devices for sysfs and debugfs */
+ unsigned int drv_index;
+
+ /* Major number for this device. Ports will be created as minors. */
+ int chr_major;
};
/* This struct holds the per-port data */
/* The IO vqs for this port */
struct virtqueue *in_vq, *out_vq;
+ /* File in the debugfs directory that exposes this port's information */
+ struct dentry *debugfs_file;
+
/*
* The entries in this struct will be valid if this port is
* hooked up to an hvc console
*/
struct console cons;
+ /* Each port associates with a separate char device */
+ struct cdev cdev;
+ struct device *dev;
+
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
+ /* The 'name' of the port that we expose via sysfs properties */
+ char *name;
+
/* The 'id' to identify the port with the Host */
u32 id;
+
+ /* Is the host device open */
+ bool host_connected;
+
+ /* We should allow only one process to open a port */
+ bool guest_connected;
};
/* This is the very early arch-specified put chars function. */
return ret;
}
+/* Discard any unread data this port has. Callers lockers. */
+static void discard_port_data(struct port *port)
+{
+ struct port_buffer *buf;
+ struct virtqueue *vq;
+ unsigned int len;
+ int ret;
+
+ vq = port->in_vq;
+ if (port->inbuf)
+ buf = port->inbuf;
+ else
+ buf = vq->vq_ops->get_buf(vq, &len);
+
+ ret = 0;
+ while (buf) {
+ if (add_inbuf(vq, buf) < 0) {
+ ret++;
+ free_buf(buf);
+ }
+ buf = vq->vq_ops->get_buf(vq, &len);
+ }
+ port->inbuf = NULL;
+ if (ret)
+ dev_warn(port->dev, "Errors adding %d buffers back to vq\n",
+ ret);
+}
+
static bool port_has_data(struct port *port)
{
unsigned long flags;
bool ret;
- ret = false;
spin_lock_irqsave(&port->inbuf_lock, flags);
- if (port->inbuf)
+ if (port->inbuf) {
+ ret = true;
+ goto out;
+ }
+ port->inbuf = get_inbuf(port);
+ if (port->inbuf) {
ret = true;
+ goto out;
+ }
+ ret = false;
+out:
spin_unlock_irqrestore(&port->inbuf_lock, flags);
-
return ret;
}
struct scatterlist sg[1];
struct virtio_console_control cpkt;
struct virtqueue *vq;
- int len;
+ unsigned int len;
if (!use_multiport(port->portdev))
return 0;
port->inbuf = NULL;
if (add_inbuf(port->in_vq, buf) < 0)
- dev_warn(&port->portdev->vdev->dev, "failed add_buf\n");
+ dev_warn(port->dev, "failed add_buf\n");
spin_unlock_irqrestore(&port->inbuf_lock, flags);
}
return out_count;
}
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct port *port)
+{
+ return port_has_data(port) || !port->host_connected;
+}
+
+static ssize_t port_fops_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (!port_has_data(port)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
+ return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
+ }
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected.
+ */
+ if (!port_has_data(port) && !port->host_connected)
+ return 0;
+
+ return fill_readbuf(port, ubuf, count, true);
+}
+
+static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct port *port;
+ char *buf;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ count = min((size_t)(32 * 1024), count);
+
+ buf = kmalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = copy_from_user(buf, ubuf, count);
+ if (ret) {
+ ret = -EFAULT;
+ goto free_buf;
+ }
+
+ ret = send_buf(port, buf, count);
+free_buf:
+ kfree(buf);
+ return ret;
+}
+
+static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
+{
+ struct port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (port->inbuf)
+ ret |= POLLIN | POLLRDNORM;
+ if (port->host_connected)
+ ret |= POLLOUT;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int port_fops_release(struct inode *inode, struct file *filp)
+{
+ struct port *port;
+
+ port = filp->private_data;
+
+ /* Notify host of port being closed */
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0);
+
+ spin_lock_irq(&port->inbuf_lock);
+ port->guest_connected = false;
+
+ discard_port_data(port);
+
+ spin_unlock_irq(&port->inbuf_lock);
+
+ return 0;
+}
+
+static int port_fops_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct port *port;
+
+ port = container_of(cdev, struct port, cdev);
+ filp->private_data = port;
+
+ /*
+ * Don't allow opening of console port devices -- that's done
+ * via /dev/hvc
+ */
+ if (is_console_port(port))
+ return -ENXIO;
+
+ /* Allow only one process to open a particular port at a time */
+ spin_lock_irq(&port->inbuf_lock);
+ if (port->guest_connected) {
+ spin_unlock_irq(&port->inbuf_lock);
+ return -EMFILE;
+ }
+
+ port->guest_connected = true;
+ spin_unlock_irq(&port->inbuf_lock);
+
+ /* Notify host of port being opened */
+ send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at
+ * /dev/vport<device number>p<port number>
+ */
+static const struct file_operations port_fops = {
+ .owner = THIS_MODULE,
+ .open = port_fops_open,
+ .read = port_fops_read,
+ .write = port_fops_write,
+ .poll = port_fops_poll,
+ .release = port_fops_release,
+};
+
/*
* The put_chars() callback is pretty straightforward.
*
struct virtio_device *vdev;
struct winsize ws;
+ /* The port could have been hot-unplugged */
+ if (!port)
+ return;
+
vdev = port->portdev->vdev;
if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE)) {
vdev->config->get(vdev,
}
}
-static void virtcons_apply_config(struct virtio_device *vdev)
-{
- resize_console(find_port_by_vtermno(0));
-}
-
/* We set the configuration at this point, since we now have a tty */
static int notifier_add_vio(struct hvc_struct *hp, int data)
{
port->cons.hvc = hvc_alloc(port->cons.vtermno, 0, &hv_ops, PAGE_SIZE);
if (IS_ERR(port->cons.hvc)) {
ret = PTR_ERR(port->cons.hvc);
+ dev_err(port->dev,
+ "error %d allocating hvc for port\n", ret);
port->cons.hvc = NULL;
return ret;
}
pdrvdata.next_vtermno++;
list_add_tail(&port->cons.list, &pdrvdata.consoles);
spin_unlock_irq(&pdrvdata_lock);
+ port->guest_connected = true;
+
+ /* Notify host of port being opened */
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
+ return 0;
+}
+
+static ssize_t show_port_name(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ struct port *port;
+
+ port = dev_get_drvdata(dev);
+
+ return sprintf(buffer, "%s\n", port->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL);
+
+static struct attribute *port_sysfs_entries[] = {
+ &dev_attr_name.attr,
+ NULL
+};
+
+static struct attribute_group port_attribute_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = port_sysfs_entries,
+};
+
+static int debugfs_open(struct inode *inode, struct file *filp)
+{
+ filp->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t debugfs_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct port *port;
+ char *buf;
+ ssize_t ret, out_offset, out_count;
+ out_count = 1024;
+ buf = kmalloc(out_count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ port = filp->private_data;
+ out_offset = 0;
+ out_offset += snprintf(buf + out_offset, out_count,
+ "name: %s\n", port->name ? port->name : "");
+ out_offset += snprintf(buf + out_offset, out_count - out_offset,
+ "guest_connected: %d\n", port->guest_connected);
+ out_offset += snprintf(buf + out_offset, out_count - out_offset,
+ "host_connected: %d\n", port->host_connected);
+ out_offset += snprintf(buf + out_offset, out_count - out_offset,
+ "is_console: %s\n",
+ is_console_port(port) ? "yes" : "no");
+ out_offset += snprintf(buf + out_offset, out_count - out_offset,
+ "console_vtermno: %u\n", port->cons.vtermno);
+
+ ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset);
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations port_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = debugfs_open,
+ .read = debugfs_read,
+};
+
+/* Remove all port-specific data. */
+static int remove_port(struct port *port)
+{
+ struct port_buffer *buf;
+
+ spin_lock_irq(&port->portdev->ports_lock);
+ list_del(&port->list);
+ spin_unlock_irq(&port->portdev->ports_lock);
+
+ if (is_console_port(port)) {
+ spin_lock_irq(&pdrvdata_lock);
+ list_del(&port->cons.list);
+ spin_unlock_irq(&pdrvdata_lock);
+ hvc_remove(port->cons.hvc);
+ }
+ if (port->guest_connected)
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0);
+
+ sysfs_remove_group(&port->dev->kobj, &port_attribute_group);
+ device_destroy(pdrvdata.class, port->dev->devt);
+ cdev_del(&port->cdev);
+
+ /* Remove unused data this port might have received. */
+ discard_port_data(port);
+
+ /* Remove buffers we queued up for the Host to send us data in. */
+ while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq)))
+ free_buf(buf);
+
+ kfree(port->name);
+
+ debugfs_remove(port->debugfs_file);
+
+ kfree(port);
return 0;
}
{
struct virtio_console_control *cpkt;
struct port *port;
+ size_t name_size;
+ int err;
cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
port->cons.hvc->irq_requested = 1;
resize_console(port);
break;
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ wake_up_interruptible(&port->waitqueue);
+ break;
+ case VIRTIO_CONSOLE_PORT_NAME:
+ /*
+ * Skip the size of the header and the cpkt to get the size
+ * of the name that was sent
+ */
+ name_size = buf->len - buf->offset - sizeof(*cpkt) + 1;
+
+ port->name = kmalloc(name_size, GFP_KERNEL);
+ if (!port->name) {
+ dev_err(port->dev,
+ "Not enough space to store port name\n");
+ break;
+ }
+ strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size - 1);
+ port->name[name_size - 1] = 0;
+
+ /*
+ * Since we only have one sysfs attribute, 'name',
+ * create it only if we have a name for the port.
+ */
+ err = sysfs_create_group(&port->dev->kobj,
+ &port_attribute_group);
+ if (err) {
+ dev_err(port->dev,
+ "Error %d creating sysfs device attributes\n",
+ err);
+ } else {
+ /*
+ * Generate a udev event so that appropriate
+ * symlinks can be created based on udev
+ * rules.
+ */
+ kobject_uevent(&port->dev->kobj, KOBJ_CHANGE);
+ }
+ break;
+ case VIRTIO_CONSOLE_PORT_REMOVE:
+ /*
+ * Hot unplug the port. We don't decrement nr_ports
+ * since we don't want to deal with extra complexities
+ * of using the lowest-available port id: We can just
+ * pick up the nr_ports number as the id and not have
+ * userspace send it to us. This helps us in two
+ * ways:
+ *
+ * - We don't need to have a 'port_id' field in the
+ * config space when a port is hot-added. This is a
+ * good thing as we might queue up multiple hotplug
+ * requests issued in our workqueue.
+ *
+ * - Another way to deal with this would have been to
+ * use a bitmap of the active ports and select the
+ * lowest non-active port from that map. That
+ * bloats the already tight config space and we
+ * would end up artificially limiting the
+ * max. number of ports to sizeof(bitmap). Right
+ * now we can support 2^32 ports (as the port id is
+ * stored in a u32 type).
+ *
+ */
+ remove_port(port);
+ break;
}
}
return;
spin_lock_irqsave(&port->inbuf_lock, flags);
- port->inbuf = get_inbuf(port);
+ if (!port->inbuf)
+ port->inbuf = get_inbuf(port);
+
+ /*
+ * Don't queue up data when port is closed. This condition
+ * can be reached when a console port is not yet connected (no
+ * tty is spawned) and the host sends out data to console
+ * ports. For generic serial ports, the host won't
+ * (shouldn't) send data till the guest is connected.
+ */
+ if (!port->guest_connected)
+ discard_port_data(port);
spin_unlock_irqrestore(&port->inbuf_lock, flags);
+ wake_up_interruptible(&port->waitqueue);
+
if (is_console_port(port) && hvc_poll(port->cons.hvc))
hvc_kick();
}
schedule_work(&portdev->control_work);
}
-static void fill_queue(struct virtqueue *vq, spinlock_t *lock)
+static void config_intr(struct virtio_device *vdev)
+{
+ struct ports_device *portdev;
+
+ portdev = vdev->priv;
+ if (use_multiport(portdev)) {
+ /* Handle port hot-add */
+ schedule_work(&portdev->config_work);
+ }
+ /*
+ * We'll use this way of resizing only for legacy support.
+ * For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+), use
+ * control messages to indicate console size changes so that
+ * it can be done per-port
+ */
+ resize_console(find_port_by_id(portdev, 0));
+}
+
+static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
{
struct port_buffer *buf;
+ unsigned int nr_added_bufs;
int ret;
+ nr_added_bufs = 0;
do {
buf = alloc_buf(PAGE_SIZE);
if (!buf)
free_buf(buf);
break;
}
+ nr_added_bufs++;
spin_unlock_irq(lock);
} while (ret > 0);
+
+ return nr_added_bufs;
}
static int add_port(struct ports_device *portdev, u32 id)
{
+ char debugfs_name[16];
struct port *port;
- struct port_buffer *inbuf;
+ struct port_buffer *buf;
+ dev_t devt;
+ unsigned int nr_added_bufs;
int err;
port = kmalloc(sizeof(*port), GFP_KERNEL);
port->portdev = portdev;
port->id = id;
+ port->name = NULL;
port->inbuf = NULL;
port->cons.hvc = NULL;
+ port->host_connected = port->guest_connected = false;
+
port->in_vq = portdev->in_vqs[port->id];
port->out_vq = portdev->out_vqs[port->id];
- spin_lock_init(&port->inbuf_lock);
+ cdev_init(&port->cdev, &port_fops);
- inbuf = alloc_buf(PAGE_SIZE);
- if (!inbuf) {
- err = -ENOMEM;
+ devt = MKDEV(portdev->chr_major, id);
+ err = cdev_add(&port->cdev, devt, 1);
+ if (err < 0) {
+ dev_err(&port->portdev->vdev->dev,
+ "Error %d adding cdev for port %u\n", err, id);
goto free_port;
}
+ port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev,
+ devt, port, "vport%up%u",
+ port->portdev->drv_index, id);
+ if (IS_ERR(port->dev)) {
+ err = PTR_ERR(port->dev);
+ dev_err(&port->portdev->vdev->dev,
+ "Error %d creating device for port %u\n",
+ err, id);
+ goto free_cdev;
+ }
+
+ spin_lock_init(&port->inbuf_lock);
+ init_waitqueue_head(&port->waitqueue);
- /* Register the input buffer the first time. */
- add_inbuf(port->in_vq, inbuf);
+ /* Fill the in_vq with buffers so the host can send us data. */
+ nr_added_bufs = fill_queue(port->in_vq, &port->inbuf_lock);
+ if (!nr_added_bufs) {
+ dev_err(port->dev, "Error allocating inbufs\n");
+ err = -ENOMEM;
+ goto free_device;
+ }
/*
* If we're not using multiport support, this has to be a console port
if (!use_multiport(port->portdev)) {
err = init_port_console(port);
if (err)
- goto free_inbuf;
+ goto free_inbufs;
}
spin_lock_irq(&portdev->ports_lock);
*/
send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
+ if (pdrvdata.debugfs_dir) {
+ /*
+ * Finally, create the debugfs file that we can use to
+ * inspect a port's state at any time
+ */
+ sprintf(debugfs_name, "vport%up%u",
+ port->portdev->drv_index, id);
+ port->debugfs_file = debugfs_create_file(debugfs_name, 0444,
+ pdrvdata.debugfs_dir,
+ port,
+ &port_debugfs_ops);
+ }
return 0;
-free_inbuf:
- free_buf(inbuf);
+free_inbufs:
+ while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq)))
+ free_buf(buf);
+free_device:
+ device_destroy(pdrvdata.class, port->dev->devt);
+free_cdev:
+ cdev_del(&port->cdev);
free_port:
kfree(port);
fail:
return err;
}
+/*
+ * The workhandler for config-space updates.
+ *
+ * This is called when ports are hot-added.
+ */
+static void config_work_handler(struct work_struct *work)
+{
+ struct virtio_console_config virtconconf;
+ struct ports_device *portdev;
+ struct virtio_device *vdev;
+ int err;
+
+ portdev = container_of(work, struct ports_device, config_work);
+
+ vdev = portdev->vdev;
+ vdev->config->get(vdev,
+ offsetof(struct virtio_console_config, nr_ports),
+ &virtconconf.nr_ports,
+ sizeof(virtconconf.nr_ports));
+
+ if (portdev->config.nr_ports == virtconconf.nr_ports) {
+ /*
+ * Port 0 got hot-added. Since we already did all the
+ * other initialisation for it, just tell the Host
+ * that the port is ready if we find the port. In
+ * case the port was hot-removed earlier, we call
+ * add_port to add the port.
+ */
+ struct port *port;
+
+ port = find_port_by_id(portdev, 0);
+ if (!port)
+ add_port(portdev, 0);
+ else
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
+ return;
+ }
+ if (virtconconf.nr_ports > portdev->config.max_nr_ports) {
+ dev_warn(&vdev->dev,
+ "More ports specified (%u) than allowed (%u)",
+ portdev->config.nr_ports + 1,
+ portdev->config.max_nr_ports);
+ return;
+ }
+ if (virtconconf.nr_ports < portdev->config.nr_ports)
+ return;
+
+ /* Hot-add ports */
+ while (virtconconf.nr_ports - portdev->config.nr_ports) {
+ err = add_port(portdev, portdev->config.nr_ports);
+ if (err)
+ break;
+ portdev->config.nr_ports++;
+ }
+}
+
static int init_vqs(struct ports_device *portdev)
{
vq_callback_t **io_callbacks;
return err;
}
+static const struct file_operations portdev_fops = {
+ .owner = THIS_MODULE,
+};
+
/*
* Once we're further in boot, we get probed like any other virtio
* device.
portdev->vdev = vdev;
vdev->priv = portdev;
+ spin_lock_irq(&pdrvdata_lock);
+ portdev->drv_index = pdrvdata.index++;
+ spin_unlock_irq(&pdrvdata_lock);
+
+ portdev->chr_major = register_chrdev(0, "virtio-portsdev",
+ &portdev_fops);
+ if (portdev->chr_major < 0) {
+ dev_err(&vdev->dev,
+ "Error %d registering chrdev for device %u\n",
+ portdev->chr_major, portdev->drv_index);
+ err = portdev->chr_major;
+ goto free;
+ }
+
multiport = false;
portdev->config.nr_ports = 1;
portdev->config.max_nr_ports = 1;
err = init_vqs(portdev);
if (err < 0) {
dev_err(&vdev->dev, "Error %d initializing vqs\n", err);
- goto free;
+ goto free_chrdev;
}
spin_lock_init(&portdev->ports_lock);
INIT_LIST_HEAD(&portdev->ports);
if (multiport) {
+ unsigned int nr_added_bufs;
+
spin_lock_init(&portdev->cvq_lock);
INIT_WORK(&portdev->control_work, &control_work_handler);
-
- fill_queue(portdev->c_ivq, &portdev->cvq_lock);
+ INIT_WORK(&portdev->config_work, &config_work_handler);
+
+ nr_added_bufs = fill_queue(portdev->c_ivq, &portdev->cvq_lock);
+ if (!nr_added_bufs) {
+ dev_err(&vdev->dev,
+ "Error allocating buffers for control queue\n");
+ err = -ENOMEM;
+ goto free_vqs;
+ }
}
for (i = 0; i < portdev->config.nr_ports; i++)
vdev->config->del_vqs(vdev);
kfree(portdev->in_vqs);
kfree(portdev->out_vqs);
+free_chrdev:
+ unregister_chrdev(portdev->chr_major, "virtio-portsdev");
free:
kfree(portdev);
fail:
return err;
}
+static void virtcons_remove(struct virtio_device *vdev)
+{
+ struct ports_device *portdev;
+ struct port *port, *port2;
+ struct port_buffer *buf;
+ unsigned int len;
+
+ portdev = vdev->priv;
+
+ cancel_work_sync(&portdev->control_work);
+ cancel_work_sync(&portdev->config_work);
+
+ list_for_each_entry_safe(port, port2, &portdev->ports, list)
+ remove_port(port);
+
+ unregister_chrdev(portdev->chr_major, "virtio-portsdev");
+
+ while ((buf = portdev->c_ivq->vq_ops->get_buf(portdev->c_ivq, &len)))
+ free_buf(buf);
+
+ while ((buf = portdev->c_ivq->vq_ops->detach_unused_buf(portdev->c_ivq)))
+ free_buf(buf);
+
+ vdev->config->del_vqs(vdev);
+ kfree(portdev->in_vqs);
+ kfree(portdev->out_vqs);
+
+ kfree(portdev);
+}
+
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID },
{ 0 },
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
- .config_changed = virtcons_apply_config,
+ .remove = virtcons_remove,
+ .config_changed = config_intr,
};
static int __init init(void)
{
+ int err;
+
+ pdrvdata.class = class_create(THIS_MODULE, "virtio-ports");
+ if (IS_ERR(pdrvdata.class)) {
+ err = PTR_ERR(pdrvdata.class);
+ pr_err("Error %d creating virtio-ports class\n", err);
+ return err;
+ }
+
+ pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
+ if (!pdrvdata.debugfs_dir) {
+ pr_warning("Error %ld creating debugfs dir for virtio-ports\n",
+ PTR_ERR(pdrvdata.debugfs_dir));
+ }
INIT_LIST_HEAD(&pdrvdata.consoles);
return register_virtio_driver(&virtio_console);
}
+
+static void __exit fini(void)
+{
+ unregister_virtio_driver(&virtio_console);
+
+ class_destroy(pdrvdata.class);
+ if (pdrvdata.debugfs_dir)
+ debugfs_remove_recursive(pdrvdata.debugfs_dir);
+}
module_init(init);
+module_exit(fini);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");