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
This commit is contained in:
commit
94ebd235c4
2 changed files with 209 additions and 48 deletions
|
@ -2,7 +2,6 @@
|
|||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/virtio_blk.h>
|
||||
|
@ -222,8 +221,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int virtblk_locked_ioctl(struct block_device *bdev, fmode_t mode,
|
||||
unsigned cmd, unsigned long data)
|
||||
static int virtblk_ioctl(struct block_device *bdev, fmode_t mode,
|
||||
unsigned int cmd, unsigned long data)
|
||||
{
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
struct virtio_blk *vblk = disk->private_data;
|
||||
|
@ -238,18 +237,6 @@ static int virtblk_locked_ioctl(struct block_device *bdev, fmode_t mode,
|
|||
(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 */
|
||||
static int virtblk_getgeo(struct block_device *bd, struct hd_geometry *geo)
|
||||
{
|
||||
|
|
|
@ -48,6 +48,9 @@ struct ports_driver_data {
|
|||
/* Used for exporting per-port information to debugfs */
|
||||
struct dentry *debugfs_dir;
|
||||
|
||||
/* List of all the devices we're handling */
|
||||
struct list_head portdevs;
|
||||
|
||||
/* Number of devices this driver is handling */
|
||||
unsigned int index;
|
||||
|
||||
|
@ -108,6 +111,9 @@ struct port_buffer {
|
|||
* ports for that device (vdev->priv).
|
||||
*/
|
||||
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
|
||||
* notification
|
||||
|
@ -178,15 +184,21 @@ struct port {
|
|||
struct console cons;
|
||||
|
||||
/* Each port associates with a separate char device */
|
||||
struct cdev cdev;
|
||||
struct cdev *cdev;
|
||||
struct device *dev;
|
||||
|
||||
/* Reference-counting to handle port hot-unplugs and file operations */
|
||||
struct kref kref;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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 */
|
||||
u32 id;
|
||||
|
||||
|
@ -221,6 +233,41 @@ out:
|
|||
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)
|
||||
{
|
||||
struct port *port;
|
||||
|
@ -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,
|
||||
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 */
|
||||
|
@ -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 */
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -575,6 +629,9 @@ static ssize_t port_fops_read(struct file *filp, char __user *ubuf,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
/* Port got hot-unplugged. */
|
||||
if (!port->guest_connected)
|
||||
return -ENODEV;
|
||||
/*
|
||||
* We could've received a disconnection message while we were
|
||||
* waiting for more data.
|
||||
|
@ -616,6 +673,9 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
/* Port got hot-unplugged. */
|
||||
if (!port->guest_connected)
|
||||
return -ENODEV;
|
||||
|
||||
count = min((size_t)(32 * 1024), count);
|
||||
|
||||
|
@ -656,6 +716,10 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
|
|||
port = filp->private_data;
|
||||
poll_wait(filp, &port->waitqueue, wait);
|
||||
|
||||
if (!port->guest_connected) {
|
||||
/* Port got unplugged */
|
||||
return POLLHUP;
|
||||
}
|
||||
ret = 0;
|
||||
if (!will_read_block(port))
|
||||
ret |= POLLIN | POLLRDNORM;
|
||||
|
@ -667,6 +731,8 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void remove_port(struct kref *kref);
|
||||
|
||||
static int port_fops_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct port *port;
|
||||
|
@ -687,6 +753,16 @@ static int port_fops_release(struct inode *inode, struct file *filp)
|
|||
reclaim_consumed_buffers(port);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -694,22 +770,31 @@ static int port_fops_open(struct inode *inode, struct file *filp)
|
|||
{
|
||||
struct cdev *cdev = inode->i_cdev;
|
||||
struct port *port;
|
||||
int ret;
|
||||
|
||||
port = container_of(cdev, struct port, cdev);
|
||||
port = find_port_by_devt(cdev->dev);
|
||||
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
|
||||
* via /dev/hvc
|
||||
*/
|
||||
if (is_console_port(port))
|
||||
return -ENXIO;
|
||||
if (is_console_port(port)) {
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
ret = -EMFILE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
port->guest_connected = true;
|
||||
|
@ -724,10 +809,23 @@ static int port_fops_open(struct inode *inode, struct file *filp)
|
|||
reclaim_consumed_buffers(port);
|
||||
spin_unlock_irq(&port->outvq_lock);
|
||||
|
||||
nonseekable_open(inode, filp);
|
||||
|
||||
/* Notify host of port being opened */
|
||||
send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1);
|
||||
|
||||
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 = {
|
|||
.write = port_fops_write,
|
||||
.poll = port_fops_poll,
|
||||
.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)
|
|||
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)
|
||||
{
|
||||
char debugfs_name[16];
|
||||
|
@ -1015,6 +1121,7 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
kref_init(&port->kref);
|
||||
|
||||
port->portdev = portdev;
|
||||
port->id = id;
|
||||
|
@ -1022,6 +1129,7 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|||
port->name = NULL;
|
||||
port->inbuf = NULL;
|
||||
port->cons.hvc = NULL;
|
||||
port->async_queue = NULL;
|
||||
|
||||
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)
|
|||
port->in_vq = portdev->in_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);
|
||||
err = cdev_add(&port->cdev, devt, 1);
|
||||
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;
|
||||
goto free_cdev;
|
||||
}
|
||||
port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev,
|
||||
devt, port, "vport%up%u",
|
||||
|
@ -1104,7 +1218,7 @@ free_inbufs:
|
|||
free_device:
|
||||
device_destroy(pdrvdata.class, port->dev->devt);
|
||||
free_cdev:
|
||||
cdev_del(&port->cdev);
|
||||
cdev_del(port->cdev);
|
||||
free_port:
|
||||
kfree(port);
|
||||
fail:
|
||||
|
@ -1113,21 +1227,45 @@ fail:
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Remove all port-specific data. */
|
||||
static int remove_port(struct port *port)
|
||||
/* No users remain, remove all port-specific data. */
|
||||
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;
|
||||
|
||||
spin_lock_irq(&port->portdev->ports_lock);
|
||||
list_del(&port->list);
|
||||
spin_unlock_irq(&port->portdev->ports_lock);
|
||||
|
||||
if (port->guest_connected) {
|
||||
port->guest_connected = false;
|
||||
port->host_connected = false;
|
||||
wake_up_interruptible(&port->waitqueue);
|
||||
send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0);
|
||||
}
|
||||
|
||||
spin_lock_irq(&port->portdev->ports_lock);
|
||||
list_del(&port->list);
|
||||
spin_unlock_irq(&port->portdev->ports_lock);
|
||||
/* Let the app know the port is going down. */
|
||||
send_sigio_to_port(port);
|
||||
}
|
||||
|
||||
if (is_console_port(port)) {
|
||||
spin_lock_irq(&pdrvdata_lock);
|
||||
|
@ -1146,9 +1284,6 @@ static int remove_port(struct port *port)
|
|||
hvc_remove(port->cons.hvc);
|
||||
#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. */
|
||||
discard_port_data(port);
|
||||
|
@ -1159,12 +1294,19 @@ static int remove_port(struct port *port)
|
|||
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
||||
free_buf(buf);
|
||||
|
||||
kfree(port->name);
|
||||
/*
|
||||
* We should just assume the device itself has gone off --
|
||||
* else a close on an open port later will try to send out a
|
||||
* control message.
|
||||
*/
|
||||
port->portdev = NULL;
|
||||
|
||||
debugfs_remove(port->debugfs_file);
|
||||
|
||||
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 */
|
||||
|
@ -1203,7 +1345,7 @@ static void handle_control_message(struct ports_device *portdev,
|
|||
add_port(portdev, cpkt->id);
|
||||
break;
|
||||
case VIRTIO_CONSOLE_PORT_REMOVE:
|
||||
remove_port(port);
|
||||
unplug_port(port);
|
||||
break;
|
||||
case VIRTIO_CONSOLE_CONSOLE_PORT:
|
||||
if (!cpkt->value)
|
||||
|
@ -1245,6 +1387,12 @@ static void handle_control_message(struct ports_device *portdev,
|
|||
spin_lock_irq(&port->outvq_lock);
|
||||
reclaim_consumed_buffers(port);
|
||||
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;
|
||||
case VIRTIO_CONSOLE_PORT_NAME:
|
||||
/*
|
||||
|
@ -1341,6 +1489,9 @@ static void in_intr(struct virtqueue *vq)
|
|||
|
||||
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))
|
||||
hvc_kick();
|
||||
}
|
||||
|
@ -1577,6 +1728,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
|
|||
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,
|
||||
VIRTIO_CONSOLE_DEVICE_READY, 1);
|
||||
return 0;
|
||||
|
@ -1600,23 +1755,41 @@ 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;
|
||||
|
||||
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);
|
||||
|
||||
list_for_each_entry_safe(port, port2, &portdev->ports, list)
|
||||
remove_port(port);
|
||||
unplug_port(port);
|
||||
|
||||
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)))
|
||||
free_buf(buf);
|
||||
while ((buf = virtqueue_get_buf(portdev->c_ivq, &len)))
|
||||
free_buf(buf);
|
||||
|
||||
while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq)))
|
||||
free_buf(buf);
|
||||
}
|
||||
|
||||
vdev->config->del_vqs(vdev);
|
||||
kfree(portdev->in_vqs);
|
||||
|
@ -1663,6 +1836,7 @@ static int __init init(void)
|
|||
PTR_ERR(pdrvdata.debugfs_dir));
|
||||
}
|
||||
INIT_LIST_HEAD(&pdrvdata.consoles);
|
||||
INIT_LIST_HEAD(&pdrvdata.portdevs);
|
||||
|
||||
return register_virtio_driver(&virtio_console);
|
||||
}
|
||||
|
|
Reference in a new issue