diff options
| author | Sjur Brændeland <sjur.brandeland@stericsson.com> | 2012-12-13 23:10:51 -0500 |
|---|---|---|
| committer | Rusty Russell <rusty@rustcorp.com.au> | 2012-12-17 23:50:44 -0500 |
| commit | 1b6370463e88b0c1c317de16d7b962acc1dab4f2 (patch) | |
| tree | 778c4e46202565fb8797921d816861c587066f5b | |
| parent | 276a3e954cfe4da7c492c9063741f99290d2973e (diff) | |
virtio_console: Add support for remoteproc serial
Add a simple serial connection driver called
VIRTIO_ID_RPROC_SERIAL (11) for communicating with a
remote processor in an asymmetric multi-processing
configuration.
This implementation reuses the existing virtio_console
implementation, and adds support for DMA allocation
of data buffers and disables use of tty console and
the virtio control queue.
Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
Acked-by: Amit Shah <amit.shah@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
| -rw-r--r-- | drivers/char/virtio_console.c | 192 | ||||
| -rw-r--r-- | include/uapi/linux/virtio_ids.h | 1 |
2 files changed, 170 insertions, 23 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 548224686963..55a89a4ae42f 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c | |||
| @@ -37,8 +37,12 @@ | |||
| 37 | #include <linux/wait.h> | 37 | #include <linux/wait.h> |
| 38 | #include <linux/workqueue.h> | 38 | #include <linux/workqueue.h> |
| 39 | #include <linux/module.h> | 39 | #include <linux/module.h> |
| 40 | #include <linux/dma-mapping.h> | ||
| 41 | #include <linux/kconfig.h> | ||
| 40 | #include "../tty/hvc/hvc_console.h" | 42 | #include "../tty/hvc/hvc_console.h" |
| 41 | 43 | ||
| 44 | #define is_rproc_enabled IS_ENABLED(CONFIG_REMOTEPROC) | ||
| 45 | |||
| 42 | /* | 46 | /* |
| 43 | * This is a global struct for storing common data for all the devices | 47 | * This is a global struct for storing common data for all the devices |
| 44 | * this driver handles. | 48 | * this driver handles. |
| @@ -112,6 +116,15 @@ struct port_buffer { | |||
| 112 | /* offset in the buf from which to consume data */ | 116 | /* offset in the buf from which to consume data */ |
| 113 | size_t offset; | 117 | size_t offset; |
| 114 | 118 | ||
| 119 | /* DMA address of buffer */ | ||
| 120 | dma_addr_t dma; | ||
| 121 | |||
| 122 | /* Device we got DMA memory from */ | ||
| 123 | struct device *dev; | ||
| 124 | |||
| 125 | /* List of pending dma buffers to free */ | ||
| 126 | struct list_head list; | ||
| 127 | |||
| 115 | /* If sgpages == 0 then buf is used */ | 128 | /* If sgpages == 0 then buf is used */ |
| 116 | unsigned int sgpages; | 129 | unsigned int sgpages; |
| 117 | 130 | ||
| @@ -331,6 +344,11 @@ static bool is_console_port(struct port *port) | |||
| 331 | return false; | 344 | return false; |
| 332 | } | 345 | } |
| 333 | 346 | ||
| 347 | static bool is_rproc_serial(const struct virtio_device *vdev) | ||
| 348 | { | ||
| 349 | return is_rproc_enabled && vdev->id.device == VIRTIO_ID_RPROC_SERIAL; | ||
| 350 | } | ||
| 351 | |||
| 334 | static inline bool use_multiport(struct ports_device *portdev) | 352 | static inline bool use_multiport(struct ports_device *portdev) |
| 335 | { | 353 | { |
| 336 | /* | 354 | /* |
| @@ -342,11 +360,13 @@ static inline bool use_multiport(struct ports_device *portdev) | |||
| 342 | return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT); | 360 | return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT); |
| 343 | } | 361 | } |
| 344 | 362 | ||
| 345 | static void free_buf(struct port_buffer *buf) | 363 | static DEFINE_SPINLOCK(dma_bufs_lock); |
| 364 | static LIST_HEAD(pending_free_dma_bufs); | ||
| 365 | |||
| 366 | static void free_buf(struct port_buffer *buf, bool can_sleep) | ||
| 346 | { | 367 | { |
| 347 | unsigned int i; | 368 | unsigned int i; |
| 348 | 369 | ||
| 349 | kfree(buf->buf); | ||
| 350 | for (i = 0; i < buf->sgpages; i++) { | 370 | for (i = 0; i < buf->sgpages; i++) { |
| 351 | struct page *page = sg_page(&buf->sg[i]); | 371 | struct page *page = sg_page(&buf->sg[i]); |
| 352 | if (!page) | 372 | if (!page) |
| @@ -354,14 +374,57 @@ static void free_buf(struct port_buffer *buf) | |||
| 354 | put_page(page); | 374 | put_page(page); |
| 355 | } | 375 | } |
| 356 | 376 | ||
| 377 | if (!buf->dev) { | ||
| 378 | kfree(buf->buf); | ||
| 379 | } else if (is_rproc_enabled) { | ||
| 380 | unsigned long flags; | ||
| 381 | |||
| 382 | /* dma_free_coherent requires interrupts to be enabled. */ | ||
| 383 | if (!can_sleep) { | ||
| 384 | /* queue up dma-buffers to be freed later */ | ||
| 385 | spin_lock_irqsave(&dma_bufs_lock, flags); | ||
| 386 | list_add_tail(&buf->list, &pending_free_dma_bufs); | ||
| 387 | spin_unlock_irqrestore(&dma_bufs_lock, flags); | ||
| 388 | return; | ||
| 389 | } | ||
| 390 | dma_free_coherent(buf->dev, buf->size, buf->buf, buf->dma); | ||
| 391 | |||
| 392 | /* Release device refcnt and allow it to be freed */ | ||
| 393 | put_device(buf->dev); | ||
| 394 | } | ||
| 395 | |||
| 357 | kfree(buf); | 396 | kfree(buf); |
| 358 | } | 397 | } |
| 359 | 398 | ||
| 399 | static void reclaim_dma_bufs(void) | ||
| 400 | { | ||
| 401 | unsigned long flags; | ||
| 402 | struct port_buffer *buf, *tmp; | ||
| 403 | LIST_HEAD(tmp_list); | ||
| 404 | |||
| 405 | if (list_empty(&pending_free_dma_bufs)) | ||
| 406 | return; | ||
| 407 | |||
| 408 | /* Create a copy of the pending_free_dma_bufs while holding the lock */ | ||
| 409 | spin_lock_irqsave(&dma_bufs_lock, flags); | ||
| 410 | list_cut_position(&tmp_list, &pending_free_dma_bufs, | ||
| 411 | pending_free_dma_bufs.prev); | ||
| 412 | spin_unlock_irqrestore(&dma_bufs_lock, flags); | ||
| 413 | |||
| 414 | /* Release the dma buffers, without irqs enabled */ | ||
| 415 | list_for_each_entry_safe(buf, tmp, &tmp_list, list) { | ||
| 416 | list_del(&buf->list); | ||
| 417 | free_buf(buf, true); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 360 | static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size, | 421 | static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size, |
| 361 | int pages) | 422 | int pages) |
| 362 | { | 423 | { |
| 363 | struct port_buffer *buf; | 424 | struct port_buffer *buf; |
| 364 | 425 | ||
| 426 | reclaim_dma_bufs(); | ||
| 427 | |||
| 365 | /* | 428 | /* |
| 366 | * Allocate buffer and the sg list. The sg list array is allocated | 429 | * Allocate buffer and the sg list. The sg list array is allocated |
| 367 | * directly after the port_buffer struct. | 430 | * directly after the port_buffer struct. |
| @@ -373,11 +436,34 @@ static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size, | |||
| 373 | 436 | ||
| 374 | buf->sgpages = pages; | 437 | buf->sgpages = pages; |
| 375 | if (pages > 0) { | 438 | if (pages > 0) { |
| 439 | buf->dev = NULL; | ||
| 376 | buf->buf = NULL; | 440 | buf->buf = NULL; |
| 377 | return buf; | 441 | return buf; |
| 378 | } | 442 | } |
| 379 | 443 | ||
| 380 | buf->buf = kmalloc(buf_size, GFP_KERNEL); | 444 | if (is_rproc_serial(vq->vdev)) { |
| 445 | /* | ||
| 446 | * Allocate DMA memory from ancestor. When a virtio | ||
| 447 | * device is created by remoteproc, the DMA memory is | ||
| 448 | * associated with the grandparent device: | ||
| 449 | * vdev => rproc => platform-dev. | ||
| 450 | * The code here would have been less quirky if | ||
| 451 | * DMA_MEMORY_INCLUDES_CHILDREN had been supported | ||
| 452 | * in dma-coherent.c | ||
| 453 | */ | ||
| 454 | if (!vq->vdev->dev.parent || !vq->vdev->dev.parent->parent) | ||
| 455 | goto free_buf; | ||
| 456 | buf->dev = vq->vdev->dev.parent->parent; | ||
| 457 | |||
| 458 | /* Increase device refcnt to avoid freeing it */ | ||
| 459 | get_device(buf->dev); | ||
| 460 | buf->buf = dma_alloc_coherent(buf->dev, buf_size, &buf->dma, | ||
| 461 | GFP_KERNEL); | ||
| 462 | } else { | ||
| 463 | buf->dev = NULL; | ||
| 464 | buf->buf = kmalloc(buf_size, GFP_KERNEL); | ||
| 465 | } | ||
| 466 | |||
| 381 | if (!buf->buf) | 467 | if (!buf->buf) |
| 382 | goto free_buf; | 468 | goto free_buf; |
| 383 | buf->len = 0; | 469 | buf->len = 0; |
| @@ -446,7 +532,7 @@ static void discard_port_data(struct port *port) | |||
| 446 | port->stats.bytes_discarded += buf->len - buf->offset; | 532 | port->stats.bytes_discarded += buf->len - buf->offset; |
| 447 | if (add_inbuf(port->in_vq, buf) < 0) { | 533 | if (add_inbuf(port->in_vq, buf) < 0) { |
| 448 | err++; | 534 | err++; |
| 449 | free_buf(buf); | 535 | free_buf(buf, false); |
| 450 | } | 536 | } |
| 451 | port->inbuf = NULL; | 537 | port->inbuf = NULL; |
| 452 | buf = get_inbuf(port); | 538 | buf = get_inbuf(port); |
| @@ -518,7 +604,7 @@ static void reclaim_consumed_buffers(struct port *port) | |||
| 518 | return; | 604 | return; |
| 519 | } | 605 | } |
| 520 | while ((buf = virtqueue_get_buf(port->out_vq, &len))) { | 606 | while ((buf = virtqueue_get_buf(port->out_vq, &len))) { |
| 521 | free_buf(buf); | 607 | free_buf(buf, false); |
| 522 | port->outvq_full = false; | 608 | port->outvq_full = false; |
| 523 | } | 609 | } |
| 524 | } | 610 | } |
| @@ -765,7 +851,7 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf, | |||
| 765 | goto out; | 851 | goto out; |
| 766 | 852 | ||
| 767 | free_buf: | 853 | free_buf: |
| 768 | free_buf(buf); | ||
