serial: add zero-copy dma i/o api

This commit is contained in:
anna 2021-03-01 16:08:01 +01:00
parent c59bd3f7c6
commit dca3e716ca
Signed by: fef
GPG key ID: EC22E476DC2D3D84
3 changed files with 47 additions and 21 deletions
arch/at91sam3x8e
include/arch
kernel/fs

View file

@ -84,31 +84,42 @@ void arch_serial_exit(struct serial_device *dev)
ssize_t arch_serial_write(struct serial_device *dev, const void *buf, size_t len)
{
struct dmabuf *dmabuf = NULL;
int ret;
struct dmabuf *dmabuf = dmabuf_create(&dev->device, len);
if (dmabuf == NULL)
return -ENOMEM;
memcpy(dmabuf->data, buf, len);
ret = serial_write_dma(dev, dmabuf);
dmabuf_put(dmabuf);
return ret;
}
ssize_t serial_write_dma(struct serial_device *dev, struct dmabuf *buf)
{
uint16_t len;
struct arch_serial_device *arch_dev = to_arch_serial_device(dev);
dmabuf_get(buf);
if (arch_dev->tx_next != NULL)
return -EBUSY;
if (len >= (1 << 16)) /* DMA uses 16-bit counters */
if (buf->len >= 0xffff)
len = 0xffff;
dmabuf = dmabuf_create(&dev->device, len);
if (dmabuf == NULL)
return -ENOMEM;
memcpy(&dmabuf->data[0], buf, len);
else
len = (uint16_t)buf->len;
if (arch_dev->tx_current == NULL) {
arch_dev->tx_current = dmabuf;
REG_UART_PDC_TPR = (uint32_t)&dmabuf->data[0];
REG_UART_PDC_TCR = (uint16_t)dmabuf->len;
arch_dev->tx_current = buf;
REG_UART_PDC_TPR = (uint32_t)buf->data;
REG_UART_PDC_TCR = len;
/* we weren't transmitting, so the interrupt was masked */
REG_UART_IER = REG_UART_IER_ENDTX_MASK;
} else {
arch_dev->tx_next = dmabuf;
REG_UART_PDC_TNPR = (uint32_t)&dmabuf->data[0];
REG_UART_PDC_TNCR = (uint16_t)dmabuf->len;
arch_dev->tx_next = buf;
REG_UART_PDC_TNPR = (uint32_t)buf->data;
REG_UART_PDC_TNCR = len;
}
return (ssize_t)len;

View file

@ -5,6 +5,7 @@
#include <arch/arch_include.h>
#include <ardix/dma.h>
#include <ardix/serial.h>
int arch_serial_init(struct serial_device *dev);
@ -21,6 +22,18 @@ void arch_serial_exit(struct serial_device *dev);
*/
ssize_t arch_serial_write(struct serial_device *dev, const void *buf, size_t len);
/**
* Directly enqueue a DMA buffer to a serial device, resulting in a zero-copy
* write. This will increment the buffer's refcount and decrement it again when
* it has been written out completely, so the caller is responsible for calling
* `dmabuf_put` as well in order to prevent a memory leak.
*
* @param dev: serial device to write to
* @param buf: raw DMA buffer to append
* @returns actual bytes that will be written, or a negative error code
*/
ssize_t serial_write_dma(struct serial_device *dev, struct dmabuf *buf);
#include ARCH_INCLUDE(serial.h)
/*

View file

@ -1,6 +1,9 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* See the end of this file for copyright, license, and warranty information. */
#include <arch/serial.h>
#include <ardix/dma.h>
#include <ardix/malloc.h>
#include <ardix/serial.h>
#include <ardix/syscall.h>
@ -13,20 +16,19 @@
ssize_t sys_write(int fd, __user const void *buf, size_t len)
{
ssize_t ret;
void *copy;
struct dmabuf *dma;
if (fd != 1) /* we only support stdout (serial console) right now */
return -EBADF;
copy = malloc(len);
if (copy == NULL)
dma = dmabuf_create(&serial_default_device->device, len);
if (dma == NULL)
return -ENOMEM;
ret = (ssize_t)copy_from_user(copy, buf, len);
copy_from_user(dma->data, buf, len);
/* TODO: reschedule if blocking */
ret = serial_write(serial_default_device, copy, (size_t)ret);
free(copy);
ret = serial_write_dma(serial_default_device, dma);
dmabuf_put(dma);
return ret;
}