/****************************************************************************** * Copyright (c) 2016 IBM Corporation * All rights reserved. * This program and the accompanying materials * are made available under the terms of the BSD License * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/bsd-license.php * * Contributors: * IBM Corporation - initial implementation *****************************************************************************/ /* * Virtio serial device definitions. * See Virtio 1.0 - 5.3 Console Device, for details */ #include #include #include #include #include #include "virtio.h" #include "virtio-serial.h" #include "virtio-internal.h" #define DRIVER_FEATURE_SUPPORT VIRTIO_F_VERSION_1 #define RX_ELEM_SIZE 4 #define RX_NUM_ELEMS 128 #define RX_Q 0 #define TX_Q 1 static struct vqs vq_rx; static struct vqs vq_tx; static uint16_t last_rx_idx; /* Last index in RX "used" ring */ int virtio_serial_init(struct virtio_device *dev) { struct vring_avail *vq_avail; int status = VIRTIO_STAT_ACKNOWLEDGE; int i; /* Reset device */ virtio_reset_device(dev); /* Acknowledge device. */ virtio_set_status(dev, status); /* Tell HV that we know how to drive the device. */ status |= VIRTIO_STAT_DRIVER; virtio_set_status(dev, status); if (dev->is_modern) { /* Negotiate features and sets FEATURES_OK if successful */ if (virtio_negotiate_guest_features(dev, DRIVER_FEATURE_SUPPORT)) goto dev_error; virtio_get_status(dev, &status); } if (virtio_queue_init_vq(dev, &vq_rx, RX_Q)) goto dev_error; /* Allocate memory for multiple receive buffers */ vq_rx.buf_mem = SLOF_alloc_mem(RX_ELEM_SIZE * RX_NUM_ELEMS); if (!vq_rx.buf_mem) { printf("virtio-serial: Failed to allocate buffers!\n"); goto dev_error; } /* Prepare receive buffer queue */ for (i = 0; i < RX_NUM_ELEMS; i++) { uint64_t addr = (uint64_t)vq_rx.buf_mem + i * RX_ELEM_SIZE; /* Descriptor for data: */ virtio_fill_desc(&vq_rx.desc[i], dev->is_modern, addr, 1, VRING_DESC_F_WRITE, 0); vq_rx.avail->ring[i] = virtio_cpu_to_modern16(dev, i); } vq_rx.avail->flags = virtio_cpu_to_modern16(dev, VRING_AVAIL_F_NO_INTERRUPT); vq_rx.avail->idx = virtio_cpu_to_modern16(dev, RX_NUM_ELEMS); sync(); last_rx_idx = virtio_modern16_to_cpu(dev, vq_rx.used->idx); if (virtio_queue_init_vq(dev, &vq_tx, TX_Q)) goto dev_error; vq_avail = virtio_get_vring_avail(dev, TX_Q); vq_avail->flags = virtio_cpu_to_modern16(dev, VRING_AVAIL_F_NO_INTERRUPT); vq_avail->idx = 0; /* Tell HV that setup succeeded */ status |= VIRTIO_STAT_DRIVER_OK; virtio_set_status(dev, status); return 1; dev_error: printf("%s: failed\n", __func__); status |= VIRTIO_STAT_FAILED; virtio_set_status(dev, status); return 0; } void virtio_serial_shutdown(struct virtio_device *dev) { /* Quiesce device */ virtio_set_status(dev, VIRTIO_STAT_FAILED); /* Reset device */ virtio_reset_device(dev); } int virtio_serial_putchar(struct virtio_device *dev, char c) { struct vring_desc *desc; int id; uint32_t vq_size, time; struct vring_desc *vq_desc; struct vring_avail *vq_avail; struct vring_used *vq_used; volatile uint16_t *current_used_idx; uint16_t last_used_idx, avail_idx; vq_size = virtio_get_qsize(dev, TX_Q); vq_desc = virtio_get_vring_desc(dev, TX_Q); vq_avail = virtio_get_vring_avail(dev, TX_Q); vq_used = virtio_get_vring_used(dev, TX_Q); avail_idx = virtio_modern16_to_cpu(dev, vq_avail->idx); last_used_idx = vq_used->idx; current_used_idx = &vq_used->idx; /* Determine descriptor index */ id = avail_idx % vq_size; /* Set up virtqueue descriptor for header */ desc = &vq_desc[id]; virtio_fill_desc(desc, dev->is_modern, (uint64_t)&c, 1, 0, 0); vq_avail->ring[avail_idx % vq_size] = virtio_cpu_to_modern16 (dev, id); mb(); vq_avail->idx = virtio_cpu_to_modern16(dev, avail_idx + 1); /* Tell HV that the queue is ready */ virtio_queue_notify(dev, TX_Q); /* Wait for host to consume the descriptor */ time = SLOF_GetTimer() + VIRTIO_TIMEOUT; while (*current_used_idx == last_used_idx) { // do something better mb(); if (time < SLOF_GetTimer()) { printf("virtio_serial_putchar failed! \n"); return 0; } } return 1; } static uint16_t last_rx_idx; /* Last index in RX "used" ring */ char virtio_serial_getchar(struct virtio_device *dev) { int id, idx; char buf[RX_NUM_ELEMS] = {0}; uint16_t avail_idx; idx = virtio_modern16_to_cpu(dev, vq_rx.used->idx); if (last_rx_idx == idx) { /* Nothing received yet */ return 0; } id = (virtio_modern32_to_cpu(dev, vq_rx.used->ring[last_rx_idx % vq_rx.size].id) + 1) % vq_rx.size; /* Copy data to destination buffer */ memcpy(buf, (void *)virtio_modern64_to_cpu(dev, vq_rx.desc[id - 1].addr), RX_ELEM_SIZE); /* Move indices to next entries */ last_rx_idx = last_rx_idx + 1; avail_idx = virtio_modern16_to_cpu(dev, vq_rx.avail->idx); vq_rx.avail->ring[avail_idx % vq_rx.size] = virtio_cpu_to_modern16(dev, id - 1); sync(); vq_rx.avail->idx = virtio_cpu_to_modern16(dev, avail_idx + 1); sync(); /* Tell HV that RX queue entry is ready */ virtio_queue_notify(dev, RX_Q); return buf[0]; } int virtio_serial_haschar(struct virtio_device *dev) { if (last_rx_idx == virtio_modern16_to_cpu(dev, vq_rx.used->idx)) return 0; else return 1; }