* notification
*/
struct work_struct control_work;
+ struct work_struct config_work;
struct list_head ports;
unsigned long flags;
spin_lock_irqsave(&portdev->ports_lock, flags);
- list_for_each_entry(port, &portdev->ports, list)
- if (port->cdev->dev == dev)
+ list_for_each_entry(port, &portdev->ports, list) {
+ if (port->cdev->dev == dev) {
+ kref_get(&port->kref);
goto out;
+ }
+ }
port = NULL;
out:
spin_unlock_irqrestore(&portdev->ports_lock, flags);
* early_init
*/
if (!portdev->vdev)
- return 0;
- return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+ return false;
+ return __virtio_test_bit(portdev->vdev, VIRTIO_CONSOLE_F_MULTIPORT);
}
static DEFINE_SPINLOCK(dma_bufs_lock);
if (!use_multiport(portdev))
return 0;
- cpkt.id = port_id;
- cpkt.event = event;
- cpkt.value = value;
+ cpkt.id = cpu_to_virtio32(portdev->vdev, port_id);
+ cpkt.event = cpu_to_virtio16(portdev->vdev, event);
+ cpkt.value = cpu_to_virtio16(portdev->vdev, value);
vq = portdev->c_ovq;
spin_lock(&portdev->c_ovq_lock);
if (virtqueue_add_outbuf(vq, sg, 1, &cpkt, GFP_ATOMIC) == 0) {
virtqueue_kick(vq);
- while (!virtqueue_get_buf(vq, &len))
+ while (!virtqueue_get_buf(vq, &len)
+ && !virtqueue_is_broken(vq))
cpu_relax();
}
spin_unlock(&portdev->c_ovq_lock);
* we need to kmalloc a GFP_ATOMIC buffer each time the
* console driver writes something out.
*/
- while (!virtqueue_get_buf(out_vq, &len))
+ while (!virtqueue_get_buf(out_vq, &len)
+ && !virtqueue_is_broken(out_vq))
cpu_relax();
done:
spin_unlock_irqrestore(&port->outvq_lock, flags);
* Give out the data that's requested from the buffer that we have
* queued up.
*/
-static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count,
- bool to_user)
+static ssize_t fill_readbuf(struct port *port, char __user *out_buf,
+ size_t out_count, bool to_user)
{
struct port_buffer *buf;
unsigned long flags;
if (ret)
return -EFAULT;
} else {
- memcpy(out_buf, buf->buf + buf->offset, out_count);
+ memcpy((__force char *)out_buf, buf->buf + buf->offset,
+ out_count);
}
buf->offset += out_count;
port = filp->private_data;
+ /* Port is hot-unplugged. */
+ if (!port->guest_connected)
+ return -ENODEV;
+
if (!port_has_data(port)) {
/*
* If nothing's connected on the host just return 0 in
if (ret < 0)
return ret;
}
- /* Port got hot-unplugged. */
+ /* Port got hot-unplugged while we were waiting above. */
if (!port->guest_connected)
return -ENODEV;
/*
} else {
/* Failback to copying a page */
struct page *page = alloc_page(GFP_KERNEL);
- char *src = buf->ops->map(pipe, buf, 1);
- char *dst;
+ char *src;
if (!page)
return -ENOMEM;
- dst = kmap(page);
offset = sd->pos & ~PAGE_MASK;
if (len + offset > PAGE_SIZE)
len = PAGE_SIZE - offset;
- memcpy(dst + offset, src + buf->offset, len);
-
- kunmap(page);
- buf->ops->unmap(pipe, buf, src);
+ src = kmap_atomic(buf->page);
+ memcpy(page_address(page) + offset, src + buf->offset, len);
+ kunmap_atomic(src);
sg_set_page(&(sgl->sg[sgl->n]), page, len, offset);
}
if (is_rproc_serial(port->out_vq->vdev))
return -EINVAL;
+ /*
+ * pipe->nrbufs == 0 means there are no data to transfer,
+ * so this returns just 0 for no data.
+ */
+ pipe_lock(pipe);
+ if (!pipe->nrbufs) {
+ ret = 0;
+ goto error_out;
+ }
+
ret = wait_port_writable(port, filp->f_flags & O_NONBLOCK);
if (ret < 0)
- return ret;
+ goto error_out;
buf = alloc_buf(port->out_vq, 0, pipe->nrbufs);
- if (!buf)
- return -ENOMEM;
+ if (!buf) {
+ ret = -ENOMEM;
+ goto error_out;
+ }
sgl.n = 0;
sgl.len = 0;
sgl.sg = buf->sg;
sg_init_table(sgl.sg, sgl.size);
ret = __splice_from_pipe(pipe, &sd, pipe_to_sg);
+ pipe_unlock(pipe);
if (likely(ret > 0))
ret = __send_to_port(port, buf->sg, sgl.n, sgl.len, buf, true);
if (unlikely(ret <= 0))
free_buf(buf, true);
return ret;
+
+error_out:
+ pipe_unlock(pipe);
+ return ret;
}
static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
struct port *port;
int ret;
+ /* We get the port with a kref here */
port = find_port_by_devt(cdev->dev);
+ if (!port) {
+ /* Port was unplugged before we could proceed */
+ return -ENXIO;
+ }
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 we don't have an input queue yet, we can't get input. */
BUG_ON(!port->in_vq);
- return fill_readbuf(port, buf, count, false);
+ return fill_readbuf(port, (__force char __user *)buf, count, false);
}
static void resize_console(struct port *port)
* 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->vdev->index, id);
+ snprintf(debugfs_name, sizeof(debugfs_name), "vport%up%u",
+ port->portdev->vdev->index, id);
port->debugfs_file = debugfs_create_file(debugfs_name, 0444,
pdrvdata.debugfs_dir,
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);
}
{
struct port_buffer *buf;
+ spin_lock_irq(&port->inbuf_lock);
/* Remove unused data this port might have received. */
discard_port_data(port);
- reclaim_consumed_buffers(port);
-
/* Remove buffers we queued up for the Host to send us data in. */
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
free_buf(buf, true);
+ spin_unlock_irq(&port->inbuf_lock);
+
+ spin_lock_irq(&port->outvq_lock);
+ reclaim_consumed_buffers(port);
/* Free pending buffers from the out-queue. */
while ((buf = virtqueue_detach_unused_buf(port->out_vq)))
free_buf(buf, true);
+ spin_unlock_irq(&port->outvq_lock);
}
/*
list_del(&port->list);
spin_unlock_irq(&port->portdev->ports_lock);
+ spin_lock_irq(&port->inbuf_lock);
if (port->guest_connected) {
+ /* Let the app know the port is going down. */
+ send_sigio_to_port(port);
+
+ /* Do this after sigio is actually sent */
port->guest_connected = false;
port->host_connected = false;
- wake_up_interruptible(&port->waitqueue);
- /* Let the app know the port is going down. */
- send_sigio_to_port(port);
+ wake_up_interruptible(&port->waitqueue);
}
+ spin_unlock_irq(&port->inbuf_lock);
if (is_console_port(port)) {
spin_lock_irq(&pdrvdata_lock);
*/
port->portdev = NULL;
+ sysfs_remove_group(&port->dev->kobj, &port_attribute_group);
+ device_destroy(pdrvdata.class, port->dev->devt);
+ cdev_del(port->cdev);
+
+ debugfs_remove(port->debugfs_file);
+ kfree(port->name);
+
/*
* Locks around here are not necessary - a port can't be
* opened after we removed the port struct from ports_list
}
/* Any private messages that the Host and Guest want to share */
-static void handle_control_message(struct ports_device *portdev,
+static void handle_control_message(struct virtio_device *vdev,
+ struct ports_device *portdev,
struct port_buffer *buf)
{
struct virtio_console_control *cpkt;
cpkt = (struct virtio_console_control *)(buf->buf + buf->offset);
- port = find_port_by_id(portdev, cpkt->id);
- if (!port && cpkt->event != VIRTIO_CONSOLE_PORT_ADD) {
+ port = find_port_by_id(portdev, virtio32_to_cpu(vdev, cpkt->id));
+ if (!port &&
+ cpkt->event != cpu_to_virtio16(vdev, VIRTIO_CONSOLE_PORT_ADD)) {
/* No valid header at start of buffer. Drop it. */
dev_dbg(&portdev->vdev->dev,
"Invalid index %u in control packet\n", cpkt->id);
return;
}
- switch (cpkt->event) {
+ switch (virtio16_to_cpu(vdev, cpkt->event)) {
case VIRTIO_CONSOLE_PORT_ADD:
if (port) {
dev_dbg(&portdev->vdev->dev,
send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
break;
}
- if (cpkt->id >= portdev->config.max_nr_ports) {
+ if (virtio32_to_cpu(vdev, cpkt->id) >=
+ portdev->config.max_nr_ports) {
dev_warn(&portdev->vdev->dev,
- "Request for adding port with out-of-bound id %u, max. supported id: %u\n",
+ "Request for adding port with "
+ "out-of-bound id %u, max. supported id: %u\n",
cpkt->id, portdev->config.max_nr_ports - 1);
break;
}
- add_port(portdev, cpkt->id);
+ add_port(portdev, virtio32_to_cpu(vdev, cpkt->id));
break;
case VIRTIO_CONSOLE_PORT_REMOVE:
unplug_port(port);
break;
}
case VIRTIO_CONSOLE_PORT_OPEN:
- port->host_connected = cpkt->value;
+ port->host_connected = virtio16_to_cpu(vdev, cpkt->value);
wake_up_interruptible(&port->waitqueue);
/*
* If the host port got closed and the host had any
* If the guest is connected, it'll be interested in
* knowing the host connection state changed.
*/
+ spin_lock_irq(&port->inbuf_lock);
send_sigio_to_port(port);
+ spin_unlock_irq(&port->inbuf_lock);
break;
case VIRTIO_CONSOLE_PORT_NAME:
/*
buf->len = len;
buf->offset = 0;
- handle_control_message(portdev, buf);
+ handle_control_message(vq->vdev, portdev, buf);
spin_lock(&portdev->c_ivq_lock);
if (add_inbuf(portdev->c_ivq, buf) < 0) {
if (!port->guest_connected && !is_rproc_serial(port->portdev->vdev))
discard_port_data(port);
+ /* Send a SIGIO indicating new data in case the process asked for it */
+ send_sigio_to_port(port);
+
spin_unlock_irqrestore(&port->inbuf_lock, flags);
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();
}
portdev = vdev->priv;
+ if (!use_multiport(portdev))
+ schedule_work(&portdev->config_work);
+}
+
+static void config_work_handler(struct work_struct *work)
+{
+ struct ports_device *portdev;
+
+ portdev = container_of(work, struct ports_device, control_work);
if (!use_multiport(portdev)) {
+ struct virtio_device *vdev;
struct port *port;
u16 rows, cols;
- vdev->config->get(vdev,
- offsetof(struct virtio_console_config, cols),
- &cols, sizeof(u16));
- vdev->config->get(vdev,
- offsetof(struct virtio_console_config, rows),
- &rows, sizeof(u16));
+ vdev = portdev->vdev;
+ virtio_cread(vdev, struct virtio_console_config, cols, &cols);
+ virtio_cread(vdev, struct virtio_console_config, rows, &rows);
port = find_port_by_id(portdev, 0);
set_console_size(port, rows, cols);
bool multiport;
bool early = early_put_chars != NULL;
+ /* We only need a config space if features are offered */
+ if (!vdev->config->get &&
+ (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE)
+ || virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT))) {
+ dev_err(&vdev->dev, "%s failure: config access disabled\n",
+ __func__);
+ return -EINVAL;
+ }
+
/* Ensure to read early_put_chars now */
barrier();
/* Don't test MULTIPORT at all if we're rproc: not a valid feature! */
if (!is_rproc_serial(vdev) &&
- virtio_config_val(vdev, VIRTIO_CONSOLE_F_MULTIPORT,
- offsetof(struct virtio_console_config,
- max_nr_ports),
- &portdev->config.max_nr_ports) == 0) {
+ virtio_cread_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT,
+ struct virtio_console_config, max_nr_ports,
+ &portdev->config.max_nr_ports) == 0) {
multiport = true;
}
spin_lock_init(&portdev->ports_lock);
INIT_LIST_HEAD(&portdev->ports);
+ virtio_device_ready(portdev->vdev);
+
+ INIT_WORK(&portdev->config_work, &config_work_handler);
+ INIT_WORK(&portdev->control_work, &control_work_handler);
+
if (multiport) {
unsigned int nr_added_bufs;
spin_lock_init(&portdev->c_ivq_lock);
spin_lock_init(&portdev->c_ovq_lock);
- INIT_WORK(&portdev->control_work, &control_work_handler);
nr_added_bufs = fill_queue(portdev->c_ivq,
&portdev->c_ivq_lock);
/* Finish up work that's lined up */
if (use_multiport(portdev))
cancel_work_sync(&portdev->control_work);
+ else
+ cancel_work_sync(&portdev->config_work);
list_for_each_entry_safe(port, port2, &portdev->ports, list)
unplug_port(port);
static unsigned int rproc_serial_features[] = {
};
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
static int virtcons_freeze(struct virtio_device *vdev)
{
struct ports_device *portdev;
virtqueue_disable_cb(portdev->c_ivq);
cancel_work_sync(&portdev->control_work);
+ cancel_work_sync(&portdev->config_work);
/*
* Once more: if control_work_handler() was running, it would
* enable the cb as the last step.
if (ret)
return ret;
+ virtio_device_ready(portdev->vdev);
+
if (use_multiport(portdev))
fill_queue(portdev->c_ivq, &portdev->c_ivq_lock);
.probe = virtcons_probe,
.remove = virtcons_remove,
.config_changed = config_intr,
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
.freeze = virtcons_freeze,
.restore = virtcons_restore,
#endif
}
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));
- }
+ if (!pdrvdata.debugfs_dir)
+ pr_warning("Error creating debugfs dir for virtio-ports\n");
INIT_LIST_HEAD(&pdrvdata.consoles);
INIT_LIST_HEAD(&pdrvdata.portdevs);
unregister:
unregister_virtio_driver(&virtio_console);
free:
- if (pdrvdata.debugfs_dir)
- debugfs_remove_recursive(pdrvdata.debugfs_dir);
+ debugfs_remove_recursive(pdrvdata.debugfs_dir);
class_destroy(pdrvdata.class);
return err;
}
unregister_virtio_driver(&virtio_rproc_serial);
class_destroy(pdrvdata.class);
- if (pdrvdata.debugfs_dir)
- debugfs_remove_recursive(pdrvdata.debugfs_dir);
+ debugfs_remove_recursive(pdrvdata.debugfs_dir);
}
module_init(init);
module_exit(fini);