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:
Linus Torvalds 2010-10-21 12:40:33 -07:00
Родитель f6f0a6d6a7 fe5a50a10c
Коммит 94ebd235c4
2 изменённых файлов: 209 добавлений и 48 удалений

Просмотреть файл

@ -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);
}