diff --git a/dev/virtio/net/include/dev/virtio/net.h b/dev/virtio/net/include/dev/virtio/net.h new file mode 100644 index 00000000..48ac6a08 --- /dev/null +++ b/dev/virtio/net/include/dev/virtio/net.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015 Travis Geiselbrecht + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include +#include +#include + +status_t virtio_net_init(struct virtio_device *dev, uint32_t host_features) __NONNULL(); +status_t virtio_net_start(void); + +/* return the count of virtio interfaces found */ +int virtio_net_found(void); + +status_t virtio_net_get_mac_addr(uint8_t mac_addr[6]); + +struct pktbuf; +extern status_t virtio_net_send_minip_pkt(struct pktbuf *p); + diff --git a/dev/virtio/net/rules.mk b/dev/virtio/net/rules.mk index 68cb9878..14343423 100644 --- a/dev/virtio/net/rules.mk +++ b/dev/virtio/net/rules.mk @@ -9,6 +9,7 @@ MODULE_SRCS += \ $(LOCAL_DIR)/virtio-net.c MODULE_DEPS += \ - dev/virtio + dev/virtio \ + lib/minip include make/module.mk diff --git a/dev/virtio/net/virtio-net.c b/dev/virtio/net/virtio-net.c index e69de29b..02db93cd 100644 --- a/dev/virtio/net/virtio-net.c +++ b/dev/virtio/net/virtio-net.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2015 Travis Geiselbrecht + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOCAL_TRACE 0 + +struct virtio_net_config { + uint8_t mac[6]; + uint16_t status; + uint16_t max_virtqueue_pairs; +} __PACKED; + +struct virtio_net_hdr { + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; + uint16_t num_buffers; // unused in tx +} __PACKED; + +#define VIRTIO_NET_F_CSUM (1<<0) +#define VIRTIO_NET_F_GUEST_CSUM (1<<1) +#define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS (1<<2) +#define VIRTIO_NET_F_MAC (1<<5) +#define VIRTIO_NET_F_GSO (1<<6) +#define VIRTIO_NET_F_GUEST_TSO4 (1<<7) +#define VIRTIO_NET_F_GUEST_TSO6 (1<<8) +#define VIRTIO_NET_F_GUEST_ECN (1<<9) +#define VIRTIO_NET_F_GUEST_UFO (1<<10) +#define VIRTIO_NET_F_HOST_TSO4 (1<<11) +#define VIRTIO_NET_F_HOST_TSO6 (1<<12) +#define VIRTIO_NET_F_HOST_ECN (1<<13) +#define VIRTIO_NET_F_HOST_UFO (1<<14) +#define VIRTIO_NET_F_MRG_RXBUF (1<<15) +#define VIRTIO_NET_F_STATUS (1<<16) +#define VIRTIO_NET_F_CTRL_VQ (1<<17) +#define VIRTIO_NET_F_CTRL_RX (1<<18) +#define VIRTIO_NET_F_CTRL_VLAN (1<<19) +#define VIRTIO_NET_F_GUEST_ANNOUNCE (1<<21) +#define VIRTIO_NET_F_MQ (1<<22) +#define VIRTIO_NET_F_CTRL_MAC_ADDR (1<<23) + +#define VIRTIO_NET_S_LINK_UP (1<<0) +#define VIRTIO_NET_S_ANNOUNCE (1<<1) + +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 16 + +#define RING_RX 0 +#define RING_TX 1 + +struct virtio_net_dev { + struct virtio_device *dev; + bool started; + + struct virtio_net_config *config; + + spin_lock_t lock; + event_t rx_event; + + /* list of active tx/rx packets to be freed at irq time */ + pktbuf_t *pending_tx_packet[TX_RING_SIZE]; + pktbuf_t *pending_rx_packet[RX_RING_SIZE]; + + uint tx_pending_count; + struct list_node completed_rx_queue; +}; + +static enum handler_return virtio_net_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e); +static int virtio_net_rx_worker(void *arg); +static status_t virtio_net_queue_rx(struct virtio_net_dev *ndev, pktbuf_t *p); + +// XXX remove need for this +static struct virtio_net_dev *the_ndev; + +status_t virtio_net_init(struct virtio_device *dev, uint32_t host_features) +{ + LTRACEF("dev %p, host_features 0x%x\n", dev, host_features); + + /* allocate a new net device */ + struct virtio_net_dev *ndev = calloc(1, sizeof(struct virtio_net_dev)); + if (!ndev) + return ERR_NO_MEMORY; + + ndev->dev = dev; + dev->priv = ndev; + ndev->started = false; + + ndev->lock = SPIN_LOCK_INITIAL_VALUE; + event_init(&ndev->rx_event, false, EVENT_FLAG_AUTOUNSIGNAL); + list_initialize(&ndev->completed_rx_queue); + + ndev->config = (struct virtio_net_config *)dev->config_ptr; + + /* ack and set the driver status bit */ + virtio_status_acknowledge_driver(dev); + + // XXX check features bits and ack/nak them + + /* set our irq handler */ + dev->irq_driver_callback = &virtio_net_irq_driver_callback; + + /* set DRIVER_OK */ + virtio_status_driver_ok(dev); + + /* allocate a pair of virtio rings */ + virtio_alloc_ring(dev, RING_RX, RX_RING_SIZE); // rx + virtio_alloc_ring(dev, RING_TX, TX_RING_SIZE); // tx + + the_ndev = ndev; + + return NO_ERROR; +} + +status_t virtio_net_start(void) +{ + if (the_ndev->started) + return ERR_ALREADY_STARTED; + + the_ndev->started = true; + + /* start the rx worker thread */ + thread_resume(thread_create("virtio_net_rx", &virtio_net_rx_worker, (void *)the_ndev, HIGH_PRIORITY, DEFAULT_STACK_SIZE)); + + /* queue up a bunch of rxes */ + for (uint i = 0; i < RX_RING_SIZE - 1; i++) { + pktbuf_t *p = pktbuf_alloc(); + if (p) { + virtio_net_queue_rx(the_ndev, p); + } + } + + return NO_ERROR; +} + +static status_t virtio_net_queue_tx(struct virtio_net_dev *ndev, void *buf, size_t len) +{ + struct virtio_device *vdev = ndev->dev; + + uint16_t i; + pktbuf_t *p; + pktbuf_t *p2; + + DEBUG_ASSERT(ndev); + DEBUG_ASSERT(buf); + + p = pktbuf_alloc(); + if (!p) + return ERR_NO_MEMORY; + + p2 = pktbuf_alloc(); + if (!p2) { + pktbuf_free(p, true); + return ERR_NO_MEMORY; + } + + /* point our header to the base of the first pktbuf */ + p->data = p->buffer; + struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)p->data; + p->dlen = sizeof(*hdr) - 2; // num_buffers field is unused in tx + memset(hdr, 0, p->dlen); + + /* copy the outgoing packet into the second pktbuf */ + p2->data = p2->buffer; + p2->dlen = len; + memcpy(p2->data, buf, len); + + spin_lock_saved_state_t state; + spin_lock_irqsave(&ndev->lock, state); + + /* only queue if we have enough tx descriptors */ + if (ndev->tx_pending_count + 2 > TX_RING_SIZE) + goto nodesc; + + /* allocate a chain of descriptors for our transfer */ + struct vring_desc *desc = virtio_alloc_desc_chain(vdev, RING_TX, 2, &i); + if (!desc) { + spin_unlock_irqrestore(&ndev->lock, state); + +nodesc: + TRACEF("out of virtio tx descriptors, tx_pending_count %u\n", ndev->tx_pending_count); + pktbuf_free(p2, false); + pktbuf_free(p, true); + + return ERR_NO_MEMORY; + } + + ndev->tx_pending_count += 2; + + /* save a pointer to our pktbufs for the irq handler to free */ + LTRACEF("saving pointer to pkt in index %u and %u\n", i, desc->next); + DEBUG_ASSERT(ndev->pending_tx_packet[i] == NULL); + DEBUG_ASSERT(ndev->pending_tx_packet[desc->next] == NULL); + ndev->pending_tx_packet[i] = p; + ndev->pending_tx_packet[desc->next] = p2; + + /* set up the descriptor pointing to the header */ + desc->addr = pktbuf_data_phys(p); + desc->len = p->dlen; + desc->flags |= VRING_DESC_F_NEXT; + + /* set up the descriptor pointing to the buffer */ + desc = virtio_desc_index_to_desc(vdev, RING_TX, desc->next); + desc->addr = pktbuf_data_phys(p2); + desc->len = p2->dlen; + desc->flags = 0; + + /* submit the transfer */ + virtio_submit_chain(vdev, RING_TX, i); + + /* kick it off */ + virtio_kick(vdev, RING_TX); + + spin_unlock_irqrestore(&ndev->lock, state); + + return NO_ERROR; +} + +static status_t virtio_net_queue_rx(struct virtio_net_dev *ndev, pktbuf_t *p) +{ + struct virtio_device *vdev = ndev->dev; + + DEBUG_ASSERT(ndev); + DEBUG_ASSERT(p); + + /* point our header to the base of the pktbuf */ + p->data = p->buffer; + struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)p->data; + memset(hdr, 0, sizeof(struct virtio_net_hdr)); + + p->dlen = sizeof(struct virtio_net_hdr) + 1512; + + spin_lock_saved_state_t state; + spin_lock_irqsave(&ndev->lock, state); + + /* allocate a chain of descriptors for our transfer */ + uint16_t i; + struct vring_desc *desc = virtio_alloc_desc_chain(vdev, RING_RX, 1, &i); + DEBUG_ASSERT(desc); /* shouldn't be possible not to have a descriptor ready */ + + /* save a pointer to our pktbufs for the irq handler to use */ + DEBUG_ASSERT(ndev->pending_rx_packet[i] == NULL); + ndev->pending_rx_packet[i] = p; + + /* set up the descriptor pointing to the header */ + desc->addr = pktbuf_data_phys(p); + desc->len = p->dlen; + desc->flags = VRING_DESC_F_WRITE; + + /* submit the transfer */ + virtio_submit_chain(vdev, RING_RX, i); + + /* kick it off */ + virtio_kick(vdev, RING_RX); + + spin_unlock_irqrestore(&ndev->lock, state); + + return NO_ERROR; +} + +static enum handler_return virtio_net_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e) +{ + struct virtio_net_dev *ndev = (struct virtio_net_dev *)dev->priv; + + LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len); + + spin_lock(&ndev->lock); + + /* parse our descriptor chain, add back to the free queue */ + uint16_t i = e->id; + for (;;) { + int next; + struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, i); + + if (desc->flags & VRING_DESC_F_NEXT) { + next = desc->next; + } else { + /* end of chain */ + next = -1; + } + + virtio_free_desc(dev, ring, i); + + if (ring == RING_RX) { + /* put the freed rx buffer in a queue */ + pktbuf_t *p = ndev->pending_rx_packet[i]; + ndev->pending_rx_packet[i] = NULL; + + DEBUG_ASSERT(p); + LTRACEF("rx pktbuf %p filled\n", p); + + /* trim the pktbuf according to the written length in the used element descriptor */ + // XXX is it safe? + p->dlen = e->len; + + list_add_tail(&ndev->completed_rx_queue, &p->list); + } else { // ring == RING_TX + /* free the pktbuf associated with the tx packet we just consumed */ + pktbuf_t *p = ndev->pending_tx_packet[i]; + ndev->pending_tx_packet[i] = NULL; + ndev->tx_pending_count--; + + DEBUG_ASSERT(p); + LTRACEF("freeing pktbuf %p\n", p); + + pktbuf_free(p, false); + } + + if (next < 0) + break; + i = next; + } + + spin_unlock(&ndev->lock); + + /* if rx ring, signal our event */ + if (ring == 0) { + event_signal(&ndev->rx_event, false); + } + + return INT_RESCHEDULE; +} + +static int virtio_net_rx_worker(void *arg) +{ + struct virtio_net_dev *ndev = (struct virtio_net_dev *)arg; + + for (;;) { + event_wait(&ndev->rx_event); + + /* pull some packets from the received queue */ + for (;;) { + spin_lock_saved_state_t state; + spin_lock_irqsave(&ndev->lock, state); + + pktbuf_t *p = list_remove_head_type(&ndev->completed_rx_queue, pktbuf_t, list); + + spin_unlock_irqrestore(&ndev->lock, state); + + if (!p) + break; /* nothing left in the queue, go back to waiting */ + + LTRACEF("got packet len %u\n", p->dlen); + + /* process our packet */ + struct virtio_net_hdr *hdr = pktbuf_consume(p, sizeof(struct virtio_net_hdr) - 2); + if (hdr) { + //hexdump8(p->data, p->dlen); + + /* call up into the stack */ + minip_rx_driver_callback(p); + } + + /* requeue the pktbuf in the rx queue */ + virtio_net_queue_rx(ndev, p); + } + } + return 0; +} + +int virtio_net_found(void) +{ + return the_ndev ? 1 : 0; +} + +status_t virtio_net_get_mac_addr(uint8_t mac_addr[6]) +{ + if (!the_ndev) + return ERR_NOT_FOUND; + + memcpy(mac_addr, the_ndev->config->mac, 6); + + return NO_ERROR; +} + +status_t virtio_net_send_minip_pkt(pktbuf_t *p) +{ + LTRACEF("p %p, dlen %zu, eof %u\n", p, p->dlen, p->eof); + + DEBUG_ASSERT(p && p->dlen); + + if (!p->eof) { + /* can't handle multi part packets yet */ + PANIC_UNIMPLEMENTED; + + return ERR_NOT_IMPLEMENTED; + } + + status_t err = virtio_net_queue_tx(the_ndev, p->data, p->dlen); + + pktbuf_free(p, true); + + return err; +} + diff --git a/dev/virtio/virtio.c b/dev/virtio/virtio.c index f645fb3c..79ca62bd 100644 --- a/dev/virtio/virtio.c +++ b/dev/virtio/virtio.c @@ -43,6 +43,9 @@ #if WITH_DEV_VIRTIO_BLOCK #include #endif +#if WITH_DEV_VIRTIO_NET +#include +#endif #define LOCAL_TRACE 0 @@ -83,6 +86,8 @@ static enum handler_return virtio_mmio_irq(void *arg) enum handler_return ret = INT_NO_RESCHEDULE; if (irq_status & 0x1) { /* used ring update */ + // XXX is this safe? + dev->mmio_config->interrupt_ack = 0x1; /* cycle through all the active rings */ for (uint r = 0; r < MAX_VIRTIO_RINGS; r++) { @@ -90,10 +95,10 @@ static enum handler_return virtio_mmio_irq(void *arg) continue; struct vring *ring = &dev->ring[r]; - LTRACEF("ring %u: used flags 0x%hhx idx 0x%hhx\n", r, ring->used->flags, ring->used->idx); + LTRACEF("ring %u: used flags 0x%hhx idx 0x%hhx last_used %u\n", r, ring->used->flags, ring->used->idx, ring->last_used); uint cur_idx = ring->used->idx; - for (uint i = ring->last_used; i != cur_idx; i = (i + 1) & ring->num_mask) { + for (uint i = ring->last_used; i != (cur_idx & ring->num_mask); i = (i + 1) & ring->num_mask) { LTRACEF("looking at idx %u\n", i); // process chain @@ -106,8 +111,6 @@ static enum handler_return virtio_mmio_irq(void *arg) ring->last_used = (ring->last_used + 1) & ring->num_mask; } } - // XXX is this safe? - dev->mmio_config->interrupt_ack = 0x1; } LTRACEF("exiting irq\n"); @@ -184,6 +187,23 @@ int virtio_mmio_detect(void *ptr, uint count, const uint irqs[]) } #endif // WITH_DEV_VIRTIO_BLOCK +#if WITH_DEV_VIRTIO_NET + if (mmio->device_id == 1) { // network device + LTRACEF("found net device\n"); + + dev->mmio_config = mmio; + dev->config_ptr = (void *)mmio->config; + + status_t err = virtio_net_init(dev, mmio->host_features); + if (err >= 0) { + // good device + dev->valid = true; + + if (dev->irq_driver_callback) + unmask_interrupt(dev->irq); + } + } +#endif // WITH_DEV_VIRTIO_NET if (dev->valid) found++; @@ -194,7 +214,7 @@ int virtio_mmio_detect(void *ptr, uint count, const uint irqs[]) void virtio_free_desc(struct virtio_device *dev, uint ring_index, uint16_t desc_index) { - LTRACEF("dev %p ring %u index %u\n", dev, ring_index, desc_index); + LTRACEF("dev %p ring %u index %u free_count %u\n", dev, ring_index, desc_index, dev->ring[ring_index].free_count); dev->ring[ring_index].desc[desc_index].next = dev->ring[ring_index].free_list; dev->ring[ring_index].free_list = desc_index; dev->ring[ring_index].free_count++; @@ -229,6 +249,7 @@ struct vring_desc *virtio_alloc_desc_chain(struct virtio_device *dev, uint ring_ struct vring_desc *desc = &dev->ring[ring_index].desc[i]; dev->ring[ring_index].free_list = desc->next; + dev->ring[ring_index].free_count--; if (last) { desc->flags = VRING_DESC_F_NEXT; diff --git a/platform/vexpress-a9/platform.c b/platform/vexpress-a9/platform.c index 8c831ce4..825fa7d2 100644 --- a/platform/vexpress-a9/platform.c +++ b/platform/vexpress-a9/platform.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Travis Geiselbrecht + * Copyright (c) 2012-2015 Travis Geiselbrecht * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -25,10 +25,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -38,6 +40,10 @@ #include #include "platform_p.h" +#if WITH_LIB_MINIP +#include +#endif + #define SDRAM_SIZE (512*1024*1024) // XXX get this from the emulator somehow /* initial memory mappings. parsed by start.S */ @@ -118,4 +124,28 @@ void platform_init(void) /* detect any virtio devices */ const uint virtio_irqs[] = { VIRTIO0_INT, VIRTIO1_INT, VIRTIO2_INT, VIRTIO3_INT }; virtio_mmio_detect((void *)VIRTIO_BASE, 4, virtio_irqs); + +#if WITH_LIB_MINIP + + if (virtio_net_found() > 0) { + uint8_t mac_addr[6]; + + virtio_net_get_mac_addr(mac_addr); + + TRACEF("found virtio networking interface, mac addr:\n"); + hexdump8_ex(mac_addr, 6, 0); + + /* start minip */ + minip_set_macaddr(mac_addr); + + uint32_t ip_addr = IPV4(192, 168, 0, 99); + uint32_t ip_mask = IPV4(255, 255, 255, 0); + uint32_t ip_gateway = IPV4_NONE; + + //minip_init(virtio_net_send_minip_pkt, NULL, ip_addr, ip_mask, ip_gateway); + minip_init_dhcp(virtio_net_send_minip_pkt, NULL); + + virtio_net_start(); + } +#endif } diff --git a/scripts/do-qemuarm b/scripts/do-qemuarm index 67ed69f2..13d2f6e1 100755 --- a/scripts/do-qemuarm +++ b/scripts/do-qemuarm @@ -4,18 +4,23 @@ function HELP { echo "help:" echo "-b a virtio block device" echo "-n a virtio network device" + echo "-t a virtio tap network device" echo "-h for help" echo "all arguments after -- are passed to qemu directly" exit 1 } DO_NET=0 +DO_NET_TAP=0 DO_BLOCK=0 +SUDO="" -while getopts bhn FLAG; do +while getopts bhnt FLAG; do case $FLAG in b) DO_BLOCK=1;; n) DO_NET=1;; + t) DO_NET_TAP=1 + SUDO="sudo ";; h) HELP;; \?) echo unrecognized option @@ -28,6 +33,7 @@ shift $((OPTIND-1)) ARGS=" -machine vexpress-a9 -m 512 -kernel build-vexpress-a9-test/lk.elf -nographic" BLOCK_ARGS=" -drive if=none,file=blk.bin,id=blk,format=raw -device virtio-blk-device,drive=blk" NET_ARGS=" -netdev user,id=vmnic,hostname=qemu -device virtio-net-device,netdev=vmnic" +NET_TAP_ARGS=" -netdev tap,id=vmnic -device virtio-net-device,netdev=vmnic" echo DO_BLOCK = $DO_BLOCK echo DO_NET = $DO_NET @@ -38,7 +44,10 @@ fi if [ $DO_NET == 1 ]; then ARGS+=$NET_ARGS fi +if [ $DO_NET_TAP == 1 ]; then + ARGS+=$NET_TAP_ARGS +fi make vexpress-a9-test -j4 && -echo qemu-system-arm $ARGS $@ && -qemu-system-arm $ARGS $@ +echo $SUDO qemu-system-arm $ARGS $@ && +$SUDO qemu-system-arm $ARGS $@