Commit 94ebd235 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'virtio' of git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux-2.6-for-linus

* 'virtio' of git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux-2.6-for-linus:
  virtio_blk: remove BKL leftovers
  virtio: console: Disable lseek(2) for port file operations
  virtio: console: Send SIGIO in case of port unplug
  virtio: console: Send SIGIO on new data arrival on ports
  virtio: console: Send SIGIO to processes that request it for host events
  virtio: console: Reference counting portdev structs is not needed
  virtio: console: Add reference counting for port struct
  virtio: console: Use cdev_alloc() instead of cdev_init()
  virtio: console: Add a find_port_by_devt() function
  virtio: console: Add a list of portdevs that are active
  virtio: console: open: Use a common path for error handling
  virtio: console: remove_port() should return void
  virtio: console: Make write() return -ENODEV on hot-unplug
  virtio: console: Make read() return -ENODEV on hot-unplug
  virtio: console: Unblock poll on port hot-unplug
  virtio: console: Un-block reads on chardev close
  virtio: console: Check if portdev is valid in send_control_msg()
  virtio: console: Remove control vq data only if using multiport support
  virtio: console: Reset vdev before removing device
parents f6f0a6d6 fe5a50a1
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/smp_lock.h>
#include <linux/hdreg.h> #include <linux/hdreg.h>
#include <linux/virtio.h> #include <linux/virtio.h>
#include <linux/virtio_blk.h> #include <linux/virtio_blk.h>
...@@ -222,8 +221,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str) ...@@ -222,8 +221,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str)
return err; return err;
} }
static int virtblk_locked_ioctl(struct block_device *bdev, fmode_t mode, static int virtblk_ioctl(struct block_device *bdev, fmode_t mode,
unsigned cmd, unsigned long data) unsigned int cmd, unsigned long data)
{ {
struct gendisk *disk = bdev->bd_disk; struct gendisk *disk = bdev->bd_disk;
struct virtio_blk *vblk = disk->private_data; struct virtio_blk *vblk = disk->private_data;
...@@ -238,18 +237,6 @@ static int virtblk_locked_ioctl(struct block_device *bdev, fmode_t mode, ...@@ -238,18 +237,6 @@ static int virtblk_locked_ioctl(struct block_device *bdev, fmode_t mode,
(void __user *)data); (void __user *)data);
} }
static int virtblk_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long param)
{
int ret;
lock_kernel();
ret = virtblk_locked_ioctl(bdev, mode, cmd, param);
unlock_kernel();
return ret;
}
/* We provide getgeo only to please some old bootloader/partitioning tools */ /* We provide getgeo only to please some old bootloader/partitioning tools */
static int virtblk_getgeo(struct block_device *bd, struct hd_geometry *geo) static int virtblk_getgeo(struct block_device *bd, struct hd_geometry *geo)
{ {
......
...@@ -48,6 +48,9 @@ struct ports_driver_data { ...@@ -48,6 +48,9 @@ struct ports_driver_data {
/* Used for exporting per-port information to debugfs */ /* Used for exporting per-port information to debugfs */
struct dentry *debugfs_dir; struct dentry *debugfs_dir;
/* List of all the devices we're handling */
struct list_head portdevs;
/* Number of devices this driver is handling */ /* Number of devices this driver is handling */
unsigned int index; unsigned int index;
...@@ -108,6 +111,9 @@ struct port_buffer { ...@@ -108,6 +111,9 @@ struct port_buffer {
* ports for that device (vdev->priv). * ports for that device (vdev->priv).
*/ */
struct ports_device { struct ports_device {
/* Next portdev in the list, head is in the pdrvdata struct */
struct list_head list;
/* /*
* Workqueue handlers where we process deferred work after * Workqueue handlers where we process deferred work after
* notification * notification
...@@ -178,15 +184,21 @@ struct port { ...@@ -178,15 +184,21 @@ struct port {
struct console cons; struct console cons;
/* Each port associates with a separate char device */ /* Each port associates with a separate char device */
struct cdev cdev; struct cdev *cdev;
struct device *dev; struct device *dev;
/* Reference-counting to handle port hot-unplugs and file operations */
struct kref kref;
/* A waitqueue for poll() or blocking read operations */ /* A waitqueue for poll() or blocking read operations */
wait_queue_head_t waitqueue; wait_queue_head_t waitqueue;
/* The 'name' of the port that we expose via sysfs properties */ /* The 'name' of the port that we expose via sysfs properties */
char *name; char *name;
/* We can notify apps of host connect / disconnect events via SIGIO */
struct fasync_struct *async_queue;
/* The 'id' to identify the port with the Host */ /* The 'id' to identify the port with the Host */
u32 id; u32 id;
...@@ -221,6 +233,41 @@ static struct port *find_port_by_vtermno(u32 vtermno) ...@@ -221,6 +233,41 @@ static struct port *find_port_by_vtermno(u32 vtermno)
return port; return port;
} }
static struct port *find_port_by_devt_in_portdev(struct ports_device *portdev,
dev_t dev)
{
struct port *port;
unsigned long flags;
spin_lock_irqsave(&portdev->ports_lock, flags);
list_for_each_entry(port, &portdev->ports, list)
if (port->cdev->dev == dev)
goto out;
port = NULL;
out:
spin_unlock_irqrestore(&portdev->ports_lock, flags);
return port;
}
static struct port *find_port_by_devt(dev_t dev)
{
struct ports_device *portdev;
struct port *port;
unsigned long flags;
spin_lock_irqsave(&pdrvdata_lock, flags);
list_for_each_entry(portdev, &pdrvdata.portdevs, list) {
port = find_port_by_devt_in_portdev(portdev, dev);
if (port)
goto out;
}
port = NULL;
out:
spin_unlock_irqrestore(&pdrvdata_lock, flags);
return port;
}
static struct port *find_port_by_id(struct ports_device *portdev, u32 id) static struct port *find_port_by_id(struct ports_device *portdev, u32 id)
{ {
struct port *port; struct port *port;
...@@ -410,7 +457,10 @@ static ssize_t __send_control_msg(struct ports_device *portdev, u32 port_id, ...@@ -410,7 +457,10 @@ static ssize_t __send_control_msg(struct ports_device *portdev, u32 port_id,
static ssize_t send_control_msg(struct port *port, unsigned int event, static ssize_t send_control_msg(struct port *port, unsigned int event,
unsigned int value) unsigned int value)
{ {
return __send_control_msg(port->portdev, port->id, event, value); /* Did the port get unplugged before userspace closed it? */
if (port->portdev)
return __send_control_msg(port->portdev, port->id, event, value);
return 0;
} }
/* Callers must take the port->outvq_lock */ /* Callers must take the port->outvq_lock */
...@@ -525,6 +575,10 @@ static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count, ...@@ -525,6 +575,10 @@ static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count,
/* The condition that must be true for polling to end */ /* The condition that must be true for polling to end */
static bool will_read_block(struct port *port) static bool will_read_block(struct port *port)
{ {
if (!port->guest_connected) {
/* Port got hot-unplugged. Let's exit. */
return false;
}
return !port_has_data(port) && port->host_connected; return !port_has_data(port) && port->host_connected;
} }
...@@ -575,6 +629,9 @@ static ssize_t port_fops_read(struct file *filp, char __user *ubuf, ...@@ -575,6 +629,9 @@ static ssize_t port_fops_read(struct file *filp, char __user *ubuf,
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
/* Port got hot-unplugged. */
if (!port->guest_connected)
return -ENODEV;
/* /*
* We could've received a disconnection message while we were * We could've received a disconnection message while we were
* waiting for more data. * waiting for more data.
...@@ -616,6 +673,9 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf, ...@@ -616,6 +673,9 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
/* Port got hot-unplugged. */
if (!port->guest_connected)
return -ENODEV;
count = min((size_t)(32 * 1024), count); count = min((size_t)(32 * 1024), count);
...@@ -656,6 +716,10 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait) ...@@ -656,6 +716,10 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
port = filp->private_data; port = filp->private_data;
poll_wait(filp, &port->waitqueue, wait); poll_wait(filp, &port->waitqueue, wait);
if (!port->guest_connected) {
/* Port got unplugged */
return POLLHUP;
}
ret = 0; ret = 0;
if (!will_read_block(port)) if (!will_read_block(port))
ret |= POLLIN | POLLRDNORM; ret |= POLLIN | POLLRDNORM;
...@@ -667,6 +731,8 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait) ...@@ -667,6 +731,8 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
return ret; return ret;
} }
static void remove_port(struct kref *kref);
static int port_fops_release(struct inode *inode, struct file *filp) static int port_fops_release(struct inode *inode, struct file *filp)
{ {
struct port *port; struct port *port;
...@@ -687,6 +753,16 @@ static int port_fops_release(struct inode *inode, struct file *filp) ...@@ -687,6 +753,16 @@ static int port_fops_release(struct inode *inode, struct file *filp)
reclaim_consumed_buffers(port); reclaim_consumed_buffers(port);
spin_unlock_irq(&port->outvq_lock); spin_unlock_irq(&port->outvq_lock);
/*
* Locks aren't necessary here as a port can't be opened after
* unplug, and if a port isn't unplugged, a kref would already
* exist for the port. Plus, taking ports_lock here would
* create a dependency on other locks taken by functions
* inside remove_port if we're the last holder of the port,
* creating many problems.
*/
kref_put(&port->kref, remove_port);
return 0; return 0;
} }
...@@ -694,22 +770,31 @@ static int port_fops_open(struct inode *inode, struct file *filp) ...@@ -694,22 +770,31 @@ static int port_fops_open(struct inode *inode, struct file *filp)
{ {
struct cdev *cdev = inode->i_cdev; struct cdev *cdev = inode->i_cdev;
struct port *port; struct port *port;
int ret;
port = container_of(cdev, struct port, cdev); port = find_port_by_devt(cdev->dev);
filp->private_data = port; filp->private_data = port;
/* Prevent against a port getting hot-unplugged at the same time */
spin_lock_irq(&port->portdev->ports_lock);
kref_get(&port->kref);
spin_unlock_irq(&port->portdev->ports_lock);
/* /*
* Don't allow opening of console port devices -- that's done * Don't allow opening of console port devices -- that's done
* via /dev/hvc * via /dev/hvc
*/ */
if (is_console_port(port)) if (is_console_port(port)) {
return -ENXIO; ret = -ENXIO;
goto out;
}
/* Allow only one process to open a particular port at a time */ /* Allow only one process to open a particular port at a time */
spin_lock_irq(&port->inbuf_lock); spin_lock_irq(&port->inbuf_lock);
if (port->guest_connected) { if (port->guest_connected) {
spin_unlock_irq(&port->inbuf_lock); spin_unlock_irq(&port->inbuf_lock);
return -EMFILE; ret = -EMFILE;
goto out;
} }
port->guest_connected = true; port->guest_connected = true;
...@@ -724,10 +809,23 @@ static int port_fops_open(struct inode *inode, struct file *filp) ...@@ -724,10 +809,23 @@ static int port_fops_open(struct inode *inode, struct file *filp)
reclaim_consumed_buffers(port); reclaim_consumed_buffers(port);
spin_unlock_irq(&port->outvq_lock); spin_unlock_irq(&port->outvq_lock);
nonseekable_open(inode, filp);
/* Notify host of port being opened */ /* Notify host of port being opened */
send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1); send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1);
return 0; return 0;
out:
kref_put(&port->kref, remove_port);
return ret;
}
static int port_fops_fasync(int fd, struct file *filp, int mode)
{
struct port *port;
port = filp->private_data;
return fasync_helper(fd, filp, mode, &port->async_queue);
} }
/* /*
...@@ -743,6 +841,8 @@ static const struct file_operations port_fops = { ...@@ -743,6 +841,8 @@ static const struct file_operations port_fops = {
.write = port_fops_write, .write = port_fops_write,
.poll = port_fops_poll, .poll = port_fops_poll,
.release = port_fops_release, .release = port_fops_release,
.fasync = port_fops_fasync,
.llseek = no_llseek,
}; };
/* /*
...@@ -1001,6 +1101,12 @@ static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock) ...@@ -1001,6 +1101,12 @@ static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
return nr_added_bufs; return nr_added_bufs;
} }
static void send_sigio_to_port(struct port *port)
{
if (port->async_queue && port->guest_connected)
kill_fasync(&port->async_queue, SIGIO, POLL_OUT);
}
static int add_port(struct ports_device *portdev, u32 id) static int add_port(struct ports_device *portdev, u32 id)
{ {
char debugfs_name[16]; char debugfs_name[16];
...@@ -1015,6 +1121,7 @@ static int add_port(struct ports_device *portdev, u32 id) ...@@ -1015,6 +1121,7 @@ static int add_port(struct ports_device *portdev, u32 id)
err = -ENOMEM; err = -ENOMEM;
goto fail; goto fail;
} }
kref_init(&port->kref);
port->portdev = portdev; port->portdev = portdev;
port->id = id; port->id = id;
...@@ -1022,6 +1129,7 @@ static int add_port(struct ports_device *portdev, u32 id) ...@@ -1022,6 +1129,7 @@ static int add_port(struct ports_device *portdev, u32 id)
port->name = NULL; port->name = NULL;
port->inbuf = NULL; port->inbuf = NULL;
port->cons.hvc = NULL; port->cons.hvc = NULL;
port->async_queue = NULL;
port->cons.ws.ws_row = port->cons.ws.ws_col = 0; port->cons.ws.ws_row = port->cons.ws.ws_col = 0;
...@@ -1032,14 +1140,20 @@ static int add_port(struct ports_device *portdev, u32 id) ...@@ -1032,14 +1140,20 @@ static int add_port(struct ports_device *portdev, u32 id)
port->in_vq = portdev->in_vqs[port->id]; port->in_vq = portdev->in_vqs[port->id];
port->out_vq = portdev->out_vqs[port->id]; port->out_vq = portdev->out_vqs[port->id];
cdev_init(&port->cdev, &port_fops); port->cdev = cdev_alloc();
if (!port->cdev) {
dev_err(&port->portdev->vdev->dev, "Error allocating cdev\n");
err = -ENOMEM;
goto free_port;
}
port->cdev->ops = &port_fops;
devt = MKDEV(portdev->chr_major, id); devt = MKDEV(portdev->chr_major, id);
err = cdev_add(&port->cdev, devt, 1); err = cdev_add(port->cdev, devt, 1);
if (err < 0) { if (err < 0) {
dev_err(&port->portdev->vdev->dev, dev_err(&port->portdev->vdev->dev,
"Error %d adding cdev for port %u\n", err, id); "Error %d adding cdev for port %u\n", err, id);
goto free_port; goto free_cdev;
} }
port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev, port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev,
devt, port, "vport%up%u", devt, port, "vport%up%u",
...@@ -1104,7 +1218,7 @@ static int add_port(struct ports_device *portdev, u32 id) ...@@ -1104,7 +1218,7 @@ static int add_port(struct ports_device *portdev, u32 id)
free_device: free_device:
device_destroy(pdrvdata.class, port->dev->devt); device_destroy(pdrvdata.class, port->dev->devt);
free_cdev: free_cdev:
cdev_del(&port->cdev); cdev_del(port->cdev);
free_port: free_port:
kfree(port); kfree(port);
fail: fail:
...@@ -1113,21 +1227,45 @@ static int add_port(struct ports_device *portdev, u32 id) ...@@ -1113,21 +1227,45 @@ static int add_port(struct ports_device *portdev, u32 id)
return err; return err;
} }
/* Remove all port-specific data. */ /* No users remain, remove all port-specific data. */
static int remove_port(struct port *port) static void remove_port(struct kref *kref)
{
struct port *port;
port = container_of(kref, struct port, kref);
sysfs_remove_group(&port->dev->kobj, &port_attribute_group);
device_destroy(pdrvdata.class, port->dev->devt);
cdev_del(port->cdev);
kfree(port->name);
debugfs_remove(port->debugfs_file);
kfree(port);
}
/*
* Port got unplugged. Remove port from portdev's list and drop the
* kref reference. If no userspace has this port opened, it will
* result in immediate removal the port.
*/
static void unplug_port(struct port *port)
{ {
struct port_buffer *buf; struct port_buffer *buf;
spin_lock_irq(&port->portdev->ports_lock);
list_del(&port->list);
spin_unlock_irq(&port->portdev->ports_lock);
if (port->guest_connected) { if (port->guest_connected) {
port->guest_connected = false; port->guest_connected = false;
port->host_connected = false; port->host_connected = false;
wake_up_interruptible(&port->waitqueue); wake_up_interruptible(&port->waitqueue);
send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0);
}
spin_lock_irq(&port->portdev->ports_lock); /* Let the app know the port is going down. */
list_del(&port->list); send_sigio_to_port(port);
spin_unlock_irq(&port->portdev->ports_lock); }
if (is_console_port(port)) { if (is_console_port(port)) {
spin_lock_irq(&pdrvdata_lock); spin_lock_irq(&pdrvdata_lock);
...@@ -1146,9 +1284,6 @@ static int remove_port(struct port *port) ...@@ -1146,9 +1284,6 @@ static int remove_port(struct port *port)
hvc_remove(port->cons.hvc); hvc_remove(port->cons.hvc);
#endif #endif
} }
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. */ /* Remove unused data this port might have received. */
discard_port_data(port); discard_port_data(port);
...@@ -1159,12 +1294,19 @@ static int remove_port(struct port *port) ...@@ -1159,12 +1294,19 @@ static int remove_port(struct port *port)
while ((buf = virtqueue_detach_unused_buf(port->in_vq))) while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
free_buf(buf); free_buf(buf);
kfree(port->name); /*
* We should just assume the device itself has gone off --
debugfs_remove(port->debugfs_file); * else a close on an open port later will try to send out a
* control message.
*/
port->portdev = NULL;
kfree(port); /*
return 0; * Locks around here are not necessary - a port can't be
* opened after we removed the port struct from ports_list
* above.
*/
kref_put(&port->kref, remove_port);
} }
/* Any private messages that the Host and Guest want to share */ /* Any private messages that the Host and Guest want to share */
...@@ -1203,7 +1345,7 @@ static void handle_control_message(struct ports_device *portdev, ...@@ -1203,7 +1345,7 @@ static void handle_control_message(struct ports_device *portdev,
add_port(portdev, cpkt->id); add_port(portdev, cpkt->id);
break; break;
case VIRTIO_CONSOLE_PORT_REMOVE: case VIRTIO_CONSOLE_PORT_REMOVE:
remove_port(port); unplug_port(port);
break; break;
case VIRTIO_CONSOLE_CONSOLE_PORT: case VIRTIO_CONSOLE_CONSOLE_PORT:
if (!cpkt->value) if (!cpkt->value)
...@@ -1245,6 +1387,12 @@ static void handle_control_message(struct ports_device *portdev, ...@@ -1245,6 +1387,12 @@ static void handle_control_message(struct ports_device *portdev,
spin_lock_irq(&port->outvq_lock); spin_lock_irq(&port->outvq_lock);
reclaim_consumed_buffers(port); reclaim_consumed_buffers(port);
spin_unlock_irq(&port->outvq_lock); spin_unlock_irq(&port->outvq_lock);
/*
* If the guest is connected, it'll be interested in
* knowing the host connection state changed.
*/
send_sigio_to_port(port);
break; break;
case VIRTIO_CONSOLE_PORT_NAME: case VIRTIO_CONSOLE_PORT_NAME:
/* /*
...@@ -1341,6 +1489,9 @@ static void in_intr(struct virtqueue *vq) ...@@ -1341,6 +1489,9 @@ static void in_intr(struct virtqueue *vq)
wake_up_interruptible(&port->waitqueue); wake_up_interruptible(&port->waitqueue);
/* Send a SIGIO indicating new data in case the process asked for it */
send_sigio_to_port(port);
if (is_console_port(port) && hvc_poll(port->cons.hvc)) if (is_console_port(port) && hvc_poll(port->cons.hvc))
hvc_kick(); hvc_kick();
} }
...@@ -1577,6 +1728,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev) ...@@ -1577,6 +1728,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
add_port(portdev, 0); add_port(portdev, 0);
} }
spin_lock_irq(&pdrvdata_lock);
list_add_tail(&portdev->list, &pdrvdata.portdevs);
spin_unlock_irq(&pdrvdata_lock);
__send_control_msg(portdev, VIRTIO_CONSOLE_BAD_ID, __send_control_msg(portdev, VIRTIO_CONSOLE_BAD_ID,
VIRTIO_CONSOLE_DEVICE_READY, 1); VIRTIO_CONSOLE_DEVICE_READY, 1);
return 0; return 0;
...@@ -1600,23 +1755,41 @@ static void virtcons_remove(struct virtio_device *vdev) ...@@ -1600,23 +1755,41 @@ static void virtcons_remove(struct virtio_device *vdev)
{ {
struct ports_device *portdev; struct ports_device *portdev;
struct port *port, *port2; struct port *port, *port2;
struct port_buffer *buf;
unsigned int len;
portdev = vdev->priv; portdev = vdev->priv;
spin_lock_irq(&pdrvdata_lock);
list_del(&portdev->list);
spin_unlock_irq(&pdrvdata_lock);
/* Disable interrupts for vqs */
vdev->config->reset(vdev);
/* Finish up work that's lined up */
cancel_work_sync(&portdev->control_work); cancel_work_sync(&portdev->control_work);
list_for_each_entry_safe(port, port2, &portdev->ports, list) list_for_each_entry_safe(port, port2, &portdev->ports, list)
remove_port(port); unplug_port(port);
unregister_chrdev(portdev->chr_major, "virtio-portsdev"); unregister_chrdev(portdev->chr_major, "virtio-portsdev");
while ((buf = virtqueue_get_buf(portdev->c_ivq, &len))) /*
free_buf(buf); * When yanking out a device, we immediately lose the
* (device-side) queues. So there's no point in keeping the
* guest side around till we drop our final reference. This
* also means that any ports which are in an open state will
* have to just stop using the port, as the vqs are going
* away.
*/
if (use_multiport(portdev)) {
struct port_buffer *buf;
unsigned int len;
while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq))) while ((buf = virtqueue_get_buf(portdev->c_ivq, &len)))
free_buf(buf); free_buf(buf);
while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq)))
free_buf(buf);
}
vdev->config->del_vqs(vdev); vdev->config->del_vqs(vdev);
kfree(portdev->in_vqs); kfree(portdev->in_vqs);
...@@ -1663,6 +1836,7 @@ static int __init init(void) ...@@ -1663,6 +1836,7 @@ static int __init init(void)
PTR_ERR(pdrvdata.debugfs_dir)); PTR_ERR(pdrvdata.debugfs_dir));
} }
INIT_LIST_HEAD(&pdrvdata.consoles); INIT_LIST_HEAD(&pdrvdata.consoles);
INIT_LIST_HEAD(&pdrvdata.portdevs);
return register_virtio_driver(&virtio_console); return register_virtio_driver(&virtio_console);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment