598 lines
18 KiB
C
598 lines
18 KiB
C
/****************************************************************************
|
|
* drivers/virtio/virtio-serial.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
|
|
#include <nuttx/serial/serial.h>
|
|
#include <nuttx/virtio/virtio.h>
|
|
|
|
#include "virtio-serial.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define VIRTIO_SERIAL_RX 0
|
|
#define VIRTIO_SERIAL_TX 1
|
|
#define VIRTIO_SERIAL_NUM 2
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct virtio_serial_priv_s
|
|
{
|
|
/* Virtio device informations */
|
|
|
|
FAR struct virtio_device *vdev;
|
|
|
|
/* Nuttx uart device informations */
|
|
|
|
FAR struct uart_dev_s udev;
|
|
char name[NAME_MAX];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Uart operation functions */
|
|
|
|
static int virtio_serial_setup(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_shutdown(FAR struct uart_dev_s *dev);
|
|
static int virtio_serial_attach(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_detach(FAR struct uart_dev_s *dev);
|
|
static int virtio_serial_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
static void virtio_serial_rxint(FAR struct uart_dev_s *dev, bool enable);
|
|
static void virtio_serial_send(FAR struct uart_dev_s *dev, int ch);
|
|
static void virtio_serial_txint(FAR struct uart_dev_s *dev, bool enable);
|
|
static bool virtio_serial_txready(FAR struct uart_dev_s *dev);
|
|
static bool virtio_serial_txempty(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_dmasend(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_dmatxavail(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_dmareceive(FAR struct uart_dev_s *dev);
|
|
static void virtio_serial_dmarxfree(FAR struct uart_dev_s *dev);
|
|
|
|
/* Other functions */
|
|
|
|
static void virtio_serial_rxready(FAR struct virtqueue *vq);
|
|
static void virtio_serial_txdone(FAR struct virtqueue *vq);
|
|
|
|
static int virtio_serial_probe(FAR struct virtio_device *vdev);
|
|
static void virtio_serial_remove(FAR struct virtio_device *vdev);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct virtio_driver g_virtio_serial_driver =
|
|
{
|
|
LIST_INITIAL_VALUE(g_virtio_serial_driver.node), /* node */
|
|
VIRTIO_ID_CONSOLE, /* device id */
|
|
virtio_serial_probe, /* probe */
|
|
virtio_serial_remove, /* remove */
|
|
};
|
|
|
|
static struct virtio_driver g_virtio_rprocserial_driver =
|
|
{
|
|
LIST_INITIAL_VALUE(g_virtio_rprocserial_driver.node), /* node */
|
|
VIRTIO_ID_RPROC_SERIAL, /* device id */
|
|
virtio_serial_probe, /* probe */
|
|
virtio_serial_remove, /* remove */
|
|
};
|
|
|
|
static const struct uart_ops_s g_virtio_serial_ops =
|
|
{
|
|
virtio_serial_setup, /* setup */
|
|
virtio_serial_shutdown, /* shutdown */
|
|
virtio_serial_attach, /* attach */
|
|
virtio_serial_detach, /* detach */
|
|
virtio_serial_ioctl, /* ioctl */
|
|
NULL, /* receive */
|
|
virtio_serial_rxint, /* rxint */
|
|
NULL, /* rxavailable */
|
|
#ifdef CONFIG_SERIAL_IFLOWCONTROL
|
|
NULL, /* rxflowcontrol */
|
|
#endif
|
|
virtio_serial_dmasend, /* dmasend */
|
|
virtio_serial_dmareceive, /* dmareceive */
|
|
virtio_serial_dmarxfree, /* dmarxfree */
|
|
virtio_serial_dmatxavail, /* dmatxavail */
|
|
virtio_serial_send, /* send */
|
|
virtio_serial_txint, /* txint */
|
|
virtio_serial_txready, /* txready */
|
|
virtio_serial_txempty, /* txempty */
|
|
};
|
|
|
|
static int g_virtio_serial_idx = 0;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_setup
|
|
*
|
|
* Description:
|
|
* Configure the UART baud, bits, parity, fifos, etc. This
|
|
* method is called the first time that the serial port is
|
|
* opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int virtio_serial_setup(FAR struct uart_dev_s *dev)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_shutdown
|
|
*
|
|
* Description:
|
|
* Disable the UART. This method is called when the serial
|
|
* port is closed
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_shutdown(FAR struct uart_dev_s *dev)
|
|
{
|
|
/* Nothing */
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_attach
|
|
*
|
|
* Description:
|
|
* Configure the UART to operation in interrupt driven mode. This method
|
|
* is called when the serial port is opened. Normally, this is just after
|
|
* the setup() method is called, however, the serial console may operate in
|
|
* a non-interrupt driven mode during the boot phase.
|
|
*
|
|
* RX and TX interrupts are not enabled when by the attach method (unless
|
|
* the hardware supports multiple levels of interrupt enabling). The RX
|
|
* and TX interrupts are not enabled until the txint() and rxint() methods
|
|
* are called.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int virtio_serial_attach(FAR struct uart_dev_s *dev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = dev->priv;
|
|
FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_SERIAL_RX].vq;
|
|
|
|
virtqueue_enable_cb(vq);
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_detach
|
|
*
|
|
* Description:
|
|
* Detach UART interrupts. This method is called when the serial port is
|
|
* closed normally just before the shutdown method is called. The
|
|
* exception is the serial console which is never shutdown.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_detach(FAR struct uart_dev_s *dev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = dev->priv;
|
|
FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_SERIAL_RX].vq;
|
|
|
|
virtqueue_disable_cb(vq);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_ioctl
|
|
*
|
|
* Description:
|
|
* All ioctl calls will be routed through this method
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int virtio_serial_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
return -ENOTTY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_rxint
|
|
*
|
|
* Description:
|
|
* Call to enable or disable RX interrupts
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_rxint(FAR struct uart_dev_s *dev, bool enable)
|
|
{
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_send
|
|
*
|
|
* Description:
|
|
* This method will send one byte on the UART
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_send(FAR struct uart_dev_s *dev, int ch)
|
|
{
|
|
int nexthead;
|
|
|
|
nexthead = dev->xmit.head + 1;
|
|
if (nexthead >= dev->xmit.size)
|
|
{
|
|
nexthead = 0;
|
|
}
|
|
|
|
if (nexthead != dev->xmit.tail)
|
|
{
|
|
/* No.. not full. Add the character to the TX buffer and return. */
|
|
|
|
dev->xmit.buffer[dev->xmit.head] = ch;
|
|
dev->xmit.head = nexthead;
|
|
}
|
|
|
|
uart_dmatxavail(dev);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_txint
|
|
*
|
|
* Description:
|
|
* Call to enable or disable TX interrupts
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_txint(FAR struct uart_dev_s *dev, bool enable)
|
|
{
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: uart_txready
|
|
*
|
|
* Description:
|
|
* Return true if the tranmsit fifo is not full
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool virtio_serial_txready(FAR struct uart_dev_s *dev)
|
|
{
|
|
int nexthead = dev->xmit.head + 1;
|
|
|
|
if (nexthead >= dev->xmit.size)
|
|
{
|
|
nexthead = 0;
|
|
}
|
|
|
|
return nexthead != dev->xmit.tail;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_txempty
|
|
*
|
|
* Description:
|
|
* Return true if the transmit fifo is empty
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool virtio_serial_txempty(FAR struct uart_dev_s *dev)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_dmasend
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_dmasend(FAR struct uart_dev_s *dev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = dev->priv;
|
|
FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_SERIAL_TX].vq;
|
|
FAR struct uart_dmaxfer_s *xfer = &dev->dmatx;
|
|
struct virtqueue_buf vb[2];
|
|
uintptr_t len;
|
|
int num = 1;
|
|
|
|
/* Get the total send length */
|
|
|
|
len = xfer->length + xfer->nlength;
|
|
|
|
/* Set the virtqueue buffer */
|
|
|
|
vb[0].buf = xfer->buffer;
|
|
vb[0].len = xfer->length;
|
|
|
|
if (xfer->nlength != 0)
|
|
{
|
|
vb[1].buf = xfer->nbuffer;
|
|
vb[1].len = xfer->nlength;
|
|
num = 2;
|
|
}
|
|
|
|
/* Add buffer to TX virtiqueue and notify the other size */
|
|
|
|
virtqueue_add_buffer(vq, vb, num, 0, (FAR void *)len);
|
|
virtqueue_kick(vq);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_dmatxavail
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_dmatxavail(FAR struct uart_dev_s *dev)
|
|
{
|
|
if (dev->dmatx.length == 0)
|
|
{
|
|
uart_xmitchars_dma(dev);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_dmareceive
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_dmareceive(FAR struct uart_dev_s *dev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = dev->priv;
|
|
FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_SERIAL_RX].vq;
|
|
FAR struct uart_dmaxfer_s *xfer = &dev->dmarx;
|
|
struct virtqueue_buf vb[2];
|
|
int num = 1;
|
|
|
|
vb[0].buf = xfer->buffer;
|
|
vb[0].len = xfer->length;
|
|
|
|
if (xfer->nlength != 0)
|
|
{
|
|
vb[num].buf = xfer->nbuffer;
|
|
vb[num].len = xfer->nlength;
|
|
num = 2;
|
|
}
|
|
|
|
/* Add buffer to the RX virtqueue and notify the device side */
|
|
|
|
virtqueue_add_buffer(vq, vb, 0, num, xfer);
|
|
virtqueue_kick(vq);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_dmarxfree
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_dmarxfree(FAR struct uart_dev_s *dev)
|
|
{
|
|
if (dev->dmarx.length == 0)
|
|
{
|
|
uart_recvchars_dma(dev);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_rxready
|
|
*
|
|
* Description:
|
|
* The virt serial receive virtqueue callback funtion
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_rxready(FAR struct virtqueue *vq)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = vq->vq_dev->priv;
|
|
FAR struct uart_dmaxfer_s *xfer;
|
|
uint32_t len;
|
|
|
|
/* Received some data, call uart_recvchars_done() */
|
|
|
|
xfer = virtqueue_get_buffer(vq, &len, NULL);
|
|
if (xfer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
xfer->nbytes = len;
|
|
uart_recvchars_done(&priv->udev);
|
|
uart_dmarxfree(&priv->udev);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_txdone
|
|
*
|
|
* Description:
|
|
* The virt serial transimit virtqueue callback funtion
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_txdone(FAR struct virtqueue *vq)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = vq->vq_dev->priv;
|
|
uintptr_t len;
|
|
|
|
/* Call uart_xmitchars_done to notify the upperhalf */
|
|
|
|
len = (uintptr_t)virtqueue_get_buffer(vq, NULL, NULL);
|
|
priv->udev.dmatx.nbytes = len;
|
|
uart_xmitchars_done(&priv->udev);
|
|
uart_dmatxavail(&priv->udev);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_init
|
|
****************************************************************************/
|
|
|
|
static int virtio_serial_init(FAR struct virtio_serial_priv_s *priv,
|
|
FAR struct virtio_device *vdev)
|
|
{
|
|
FAR const char *vqnames[VIRTIO_SERIAL_NUM];
|
|
vq_callback callbacks[VIRTIO_SERIAL_NUM];
|
|
FAR struct uart_dev_s *udev;
|
|
int ret;
|
|
|
|
priv->vdev = vdev;
|
|
vdev->priv = priv;
|
|
|
|
/* Uart device buffer and ops init */
|
|
|
|
udev = &priv->udev;
|
|
udev->priv = priv;
|
|
udev->ops = &g_virtio_serial_ops;
|
|
udev->recv.size = CONFIG_DRIVERS_VIRTIO_SERIAL_BUFSIZE;
|
|
udev->recv.buffer = virtio_zalloc_buf(vdev, udev->recv.size, 16);
|
|
if (udev->recv.buffer == NULL)
|
|
{
|
|
vrterr("No enough memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
udev->xmit.size = CONFIG_DRIVERS_VIRTIO_SERIAL_BUFSIZE;
|
|
udev->xmit.buffer = virtio_zalloc_buf(vdev, udev->xmit.size, 16);
|
|
if (udev->xmit.buffer == NULL)
|
|
{
|
|
vrterr("No enough memory\n");
|
|
ret = -ENOMEM;
|
|
goto err_with_recv;
|
|
}
|
|
|
|
/* Initialize the virtio device */
|
|
|
|
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER);
|
|
virtio_set_features(vdev, 0);
|
|
virtio_set_status(vdev, VIRTIO_CONFIG_FEATURES_OK);
|
|
|
|
vqnames[VIRTIO_SERIAL_RX] = "virtio_serial_rx";
|
|
vqnames[VIRTIO_SERIAL_TX] = "virtio_serial_tx";
|
|
callbacks[VIRTIO_SERIAL_RX] = virtio_serial_rxready;
|
|
callbacks[VIRTIO_SERIAL_TX] = virtio_serial_txdone;
|
|
ret = virtio_create_virtqueues(vdev, 0, VIRTIO_SERIAL_NUM, vqnames,
|
|
callbacks);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("virtio_device_create_virtqueue failed, ret=%d\n", ret);
|
|
goto err_with_xmit;
|
|
}
|
|
|
|
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
|
|
return OK;
|
|
|
|
err_with_xmit:
|
|
virtio_free_buf(vdev, udev->xmit.buffer);
|
|
err_with_recv:
|
|
virtio_free_buf(vdev, udev->recv.buffer);
|
|
virtio_reset_device(vdev);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_uninit
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_uninit(FAR struct virtio_serial_priv_s *priv)
|
|
{
|
|
FAR struct virtio_device *vdev = priv->vdev;
|
|
|
|
virtio_reset_device(vdev);
|
|
virtio_delete_virtqueues(vdev);
|
|
virtio_free_buf(vdev, priv->udev.xmit.buffer);
|
|
virtio_free_buf(vdev, priv->udev.recv.buffer);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_probe
|
|
****************************************************************************/
|
|
|
|
static int virtio_serial_probe(FAR struct virtio_device *vdev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv;
|
|
int ret;
|
|
|
|
/* Alloc the virtio serial driver and uart buffer */
|
|
|
|
priv = kmm_zalloc(sizeof(*priv));
|
|
if (priv == NULL)
|
|
{
|
|
vrterr("No enough memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = virtio_serial_init(priv, vdev);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("virtio_serial_init failed, ret=%d\n", ret);
|
|
goto err_with_priv;
|
|
}
|
|
|
|
/* Uart driver register */
|
|
|
|
snprintf(priv->name, NAME_MAX, "/dev/ttyV%d", g_virtio_serial_idx);
|
|
ret = uart_register(priv->name, &priv->udev);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("uart_register failed, ret=%d\n", ret);
|
|
goto err_with_init;
|
|
}
|
|
|
|
g_virtio_serial_idx++;
|
|
return ret;
|
|
|
|
err_with_init:
|
|
virtio_serial_uninit(priv);
|
|
err_with_priv:
|
|
kmm_free(priv);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_serial_remove
|
|
****************************************************************************/
|
|
|
|
static void virtio_serial_remove(FAR struct virtio_device *vdev)
|
|
{
|
|
FAR struct virtio_serial_priv_s *priv = vdev->priv;
|
|
|
|
unregister_driver(priv->name);
|
|
virtio_serial_uninit(priv);
|
|
kmm_free(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_register_serial_driver
|
|
****************************************************************************/
|
|
|
|
int virtio_register_serial_driver(void)
|
|
{
|
|
int ret1 = virtio_register_driver(&g_virtio_serial_driver);
|
|
int ret2 = virtio_register_driver(&g_virtio_rprocserial_driver);
|
|
return ret1 < 0 ? ret1 : ret2;
|
|
}
|