/**************************************************************************** * 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 #include #include #include #include #include #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; }