Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_virtio_net.c
Show First 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | struct pci_vtnet_softc { | ||||
pthread_mutex_t rx_mtx; | pthread_mutex_t rx_mtx; | ||||
int rx_merge; /* merged rx bufs in use */ | int rx_merge; /* merged rx bufs in use */ | ||||
pthread_t tx_tid; | pthread_t tx_tid; | ||||
pthread_mutex_t tx_mtx; | pthread_mutex_t tx_mtx; | ||||
pthread_cond_t tx_cond; | pthread_cond_t tx_cond; | ||||
int tx_in_progress; | int tx_in_progress; | ||||
size_t vhdrlen; | |||||
size_t be_vhdrlen; | |||||
struct virtio_net_config vsc_config; | struct virtio_net_config vsc_config; | ||||
struct virtio_consts vsc_consts; | struct virtio_consts vsc_consts; | ||||
}; | }; | ||||
static void pci_vtnet_reset(void *); | static void pci_vtnet_reset(void *); | ||||
/* static void pci_vtnet_notify(void *, struct vqueue_info *); */ | /* static void pci_vtnet_notify(void *, struct vqueue_info *); */ | ||||
static int pci_vtnet_cfgread(void *, int, int, uint32_t *); | static int pci_vtnet_cfgread(void *, int, int, uint32_t *); | ||||
static int pci_vtnet_cfgwrite(void *, int, int, uint32_t); | static int pci_vtnet_cfgwrite(void *, int, int, uint32_t); | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | pci_vtnet_reset(void *vsc) | ||||
*/ | */ | ||||
vi_reset_dev(&sc->vsc_vs); | vi_reset_dev(&sc->vsc_vs); | ||||
sc->resetting = 0; | sc->resetting = 0; | ||||
pthread_mutex_unlock(&sc->tx_mtx); | pthread_mutex_unlock(&sc->tx_mtx); | ||||
pthread_mutex_unlock(&sc->rx_mtx); | pthread_mutex_unlock(&sc->rx_mtx); | ||||
} | } | ||||
static __inline struct iovec * | |||||
iov_trim_hdr(struct iovec *iov, int *iovcnt, unsigned int tlen) | |||||
{ | |||||
struct iovec *riov; | |||||
if (iov[0].iov_len < tlen) { | |||||
/* | |||||
* Not enough header space in the first fragment. | |||||
* That's not ok for us. | |||||
*/ | |||||
return NULL; | |||||
} | |||||
iov[0].iov_len -= tlen; | |||||
if (iov[0].iov_len == 0) { | |||||
*iovcnt -= 1; | |||||
if (*iovcnt == 0) { | |||||
/* | |||||
* Only space for the header. That's not | |||||
* enough for us. | |||||
*/ | |||||
return NULL; | |||||
} | |||||
riov = &iov[1]; | |||||
} else { | |||||
iov[0].iov_base = (void *)((uintptr_t)iov[0].iov_base + tlen); | |||||
riov = &iov[0]; | |||||
} | |||||
return (riov); | |||||
} | |||||
struct virtio_mrg_rxbuf_info { | struct virtio_mrg_rxbuf_info { | ||||
uint16_t idx; | uint16_t idx; | ||||
uint16_t pad; | uint16_t pad; | ||||
uint32_t len; | uint32_t len; | ||||
}; | }; | ||||
static void | static void | ||||
pci_vtnet_rx(struct pci_vtnet_softc *sc) | pci_vtnet_rx(struct pci_vtnet_softc *sc) | ||||
{ | { | ||||
int prepend_hdr_len = sc->vhdrlen - sc->be_vhdrlen; | |||||
struct virtio_mrg_rxbuf_info info[VTNET_MAXSEGS]; | struct virtio_mrg_rxbuf_info info[VTNET_MAXSEGS]; | ||||
struct iovec iov[VTNET_MAXSEGS + 1]; | struct iovec iov[VTNET_MAXSEGS + 1]; | ||||
struct vqueue_info *vq; | struct vqueue_info *vq; | ||||
uint32_t cur_iov_bytes; | uint32_t riov_bytes; | ||||
struct iovec *cur_iov; | struct iovec *riov; | ||||
uint16_t cur_iov_len; | int riov_len; | ||||
uint32_t ulen; | uint32_t ulen; | ||||
int n_chains; | int n_chains; | ||||
int len; | int len; | ||||
vq = &sc->vsc_queues[VTNET_RXQ]; | vq = &sc->vsc_queues[VTNET_RXQ]; | ||||
for (;;) { | for (;;) { | ||||
struct virtio_net_rxhdr *hdr; | |||||
/* | /* | ||||
* Get a descriptor chain to store the next ingress | * Get a descriptor chain to store the next ingress | ||||
* packet. In case of mergeable rx buffers, get as | * packet. In case of mergeable rx buffers, get as | ||||
* many chains as necessary in order to make room | * many chains as necessary in order to make room | ||||
* for a maximum sized LRO packet. | * for a maximum sized LRO packet. | ||||
*/ | */ | ||||
cur_iov_bytes = 0; | riov_bytes = 0; | ||||
cur_iov_len = 0; | riov_len = 0; | ||||
cur_iov = iov; | riov = iov; | ||||
n_chains = 0; | n_chains = 0; | ||||
do { | do { | ||||
int n = vq_getchain(vq, &info[n_chains].idx, cur_iov, | int n = vq_getchain(vq, &info[n_chains].idx, riov, | ||||
VTNET_MAXSEGS - cur_iov_len, NULL); | VTNET_MAXSEGS - riov_len, NULL); | ||||
if (n == 0) { | if (n == 0) { | ||||
/* | /* | ||||
* No rx buffers. Enable RX kicks and double | * No rx buffers. Enable RX kicks and double | ||||
* check. | * check. | ||||
*/ | */ | ||||
vq_kick_enable(vq); | vq_kick_enable(vq); | ||||
if (!vq_has_descs(vq)) { | if (!vq_has_descs(vq)) { | ||||
Show All 9 Lines | do { | ||||
netbe_rx_disable(sc->vsc_be); | netbe_rx_disable(sc->vsc_be); | ||||
return; | return; | ||||
} | } | ||||
/* More rx buffers found, so keep going. */ | /* More rx buffers found, so keep going. */ | ||||
vq_kick_disable(vq); | vq_kick_disable(vq); | ||||
continue; | continue; | ||||
} | } | ||||
assert(n >= 1 && cur_iov_len + n <= VTNET_MAXSEGS); | assert(n >= 1 && riov_len + n <= VTNET_MAXSEGS); | ||||
cur_iov_len += n; | riov_len += n; | ||||
if (!sc->rx_merge) { | if (!sc->rx_merge) { | ||||
n_chains = 1; | n_chains = 1; | ||||
break; | break; | ||||
} | } | ||||
info[n_chains].len = (uint32_t)count_iov(cur_iov, n); | info[n_chains].len = (uint32_t)count_iov(riov, n); | ||||
cur_iov_bytes += info[n_chains].len; | riov_bytes += info[n_chains].len; | ||||
cur_iov += n; | riov += n; | ||||
n_chains++; | n_chains++; | ||||
} while (cur_iov_bytes < VTNET_MAX_PKT_LEN && | } while (riov_bytes < VTNET_MAX_PKT_LEN && | ||||
cur_iov_len < VTNET_MAXSEGS); | riov_len < VTNET_MAXSEGS); | ||||
len = netbe_recv(sc->vsc_be, iov, cur_iov_len); | riov = iov; | ||||
hdr = riov[0].iov_base; | |||||
if (prepend_hdr_len > 0) { | |||||
/* | |||||
* The frontend uses a virtio-net header, but the | |||||
* backend does not. We need to prepend a zeroed | |||||
* header. | |||||
*/ | |||||
riov = iov_trim_hdr(riov, &riov_len, prepend_hdr_len); | |||||
if (riov == NULL) { | |||||
/* | |||||
* The first collected chain is nonsensical, | |||||
* as it is not even enough to store the | |||||
* virtio-net header. Just drop it. | |||||
*/ | |||||
vq_relchain(vq, info[0].idx, 0); | |||||
vq_retchains(vq, n_chains - 1); | |||||
continue; | |||||
} | |||||
memset(hdr, 0, prepend_hdr_len); | |||||
} | |||||
afedorov: We need to initialize 'struct virtio_net_rxhdr *hdr = iov[0].iov_base;' before calling… | |||||
Done Inline ActionsYou're right, thank you! vmaffione: You're right, thank you! | |||||
len = netbe_recv(sc->vsc_be, riov, riov_len); | |||||
if (len <= 0) { | if (len <= 0) { | ||||
/* | /* | ||||
* No more packets (len == 0), or backend errored | * No more packets (len == 0), or backend errored | ||||
* (err < 0). Return unused available buffers | * (err < 0). Return unused available buffers | ||||
* and stop. | * and stop. | ||||
*/ | */ | ||||
vq_retchains(vq, n_chains); | vq_retchains(vq, n_chains); | ||||
/* Interrupt if needed/appropriate and stop. */ | /* Interrupt if needed/appropriate and stop. */ | ||||
vq_endchains(vq, /*used_all_avail=*/0); | vq_endchains(vq, /*used_all_avail=*/0); | ||||
return; | return; | ||||
} | } | ||||
ulen = (uint32_t)len; /* avoid too many casts below */ | ulen = (uint32_t)(len + prepend_hdr_len); | ||||
/* Publish the used buffers to the guest. */ | /* | ||||
* Publish the used buffers to the guest, reporting the | |||||
* number of bytes that we wrote. | |||||
*/ | |||||
if (!sc->rx_merge) { | if (!sc->rx_merge) { | ||||
vq_relchain(vq, info[0].idx, ulen); | vq_relchain(vq, info[0].idx, ulen); | ||||
} else { | } else { | ||||
struct virtio_net_rxhdr *hdr = iov[0].iov_base; | |||||
uint32_t iolen; | uint32_t iolen; | ||||
Done Inline ActionsI tried to test this patch. And I found a bug. So, hdr points to an incorrect location. If I move the 'hdr' declaration before calling the netbe_recv () function, then everything works correctly: afedorov: I tried to test this patch. And I found a bug.
With a tap backend, the netbe_recv() function… | |||||
Done Inline ActionsThank you for the suggestion. I will try to take a different approach to avoid visible changes to the iov vmaffione: Thank you for the suggestion. I will try to take a different approach to avoid visible changes… | |||||
int i = 0; | int i = 0; | ||||
assert(iov[0].iov_len >= sizeof(*hdr)); | |||||
do { | do { | ||||
iolen = info[i].len; | iolen = info[i].len; | ||||
if (iolen > ulen) { | if (iolen > ulen) { | ||||
iolen = ulen; | iolen = ulen; | ||||
} | } | ||||
vq_relchain_prepare(vq, info[i].idx, iolen); | vq_relchain_prepare(vq, info[i].idx, iolen); | ||||
ulen -= iolen; | ulen -= iolen; | ||||
i++; | i++; | ||||
Show All 39 Lines | pci_vtnet_ping_rxq(void *vsc, struct vqueue_info *vq) | ||||
pthread_mutex_unlock(&sc->rx_mtx); | pthread_mutex_unlock(&sc->rx_mtx); | ||||
} | } | ||||
/* TX virtqueue processing, called by the TX thread. */ | /* TX virtqueue processing, called by the TX thread. */ | ||||
static void | static void | ||||
pci_vtnet_proctx(struct pci_vtnet_softc *sc, struct vqueue_info *vq) | pci_vtnet_proctx(struct pci_vtnet_softc *sc, struct vqueue_info *vq) | ||||
{ | { | ||||
struct iovec iov[VTNET_MAXSEGS + 1]; | struct iovec iov[VTNET_MAXSEGS + 1]; | ||||
struct iovec *siov = iov; | |||||
uint16_t idx; | uint16_t idx; | ||||
ssize_t len; | ssize_t len; | ||||
int n; | int n; | ||||
/* | /* | ||||
* Obtain chain of descriptors. The first descriptor also | * Obtain chain of descriptors. The first descriptor also | ||||
* contains the virtio-net header. | * contains the virtio-net header. | ||||
*/ | */ | ||||
n = vq_getchain(vq, &idx, iov, VTNET_MAXSEGS, NULL); | n = vq_getchain(vq, &idx, iov, VTNET_MAXSEGS, NULL); | ||||
assert(n >= 1 && n <= VTNET_MAXSEGS); | assert(n >= 1 && n <= VTNET_MAXSEGS); | ||||
len = netbe_send(sc->vsc_be, iov, n); | if (sc->vhdrlen != sc->be_vhdrlen) { | ||||
/* | |||||
* The frontend uses a virtio-net header, but the backend | |||||
* does not. We simply strip the header and ignore it, as | |||||
* it should be zero-filled. | |||||
*/ | |||||
siov = iov_trim_hdr(siov, &n, sc->vhdrlen); | |||||
} | |||||
/* chain is processed, release it and set len */ | if (siov == NULL) { | ||||
vq_relchain(vq, idx, len > 0 ? len : 0); | /* The chain is nonsensical. Just drop it. */ | ||||
len = 0; | |||||
} else { | |||||
len = netbe_send(sc->vsc_be, siov, n); | |||||
if (len < 0) { | |||||
/* | |||||
* If send failed, report that 0 bytes | |||||
* were written. | |||||
*/ | |||||
len = 0; | |||||
} | } | ||||
} | |||||
/* | |||||
* Return the processed chain to the guest, reporting | |||||
* the number of bytes that we read. | |||||
*/ | |||||
vq_relchain(vq, idx, len); | |||||
} | |||||
/* Called on TX kick. */ | /* Called on TX kick. */ | ||||
static void | static void | ||||
pci_vtnet_ping_txq(void *vsc, struct vqueue_info *vq) | pci_vtnet_ping_txq(void *vsc, struct vqueue_info *vq) | ||||
{ | { | ||||
struct pci_vtnet_softc *sc = vsc; | struct pci_vtnet_softc *sc = vsc; | ||||
/* | /* | ||||
* Any ring entries to process? | * Any ring entries to process? | ||||
▲ Show 20 Lines • Show All 98 Lines • ▼ Show 20 Lines | |||||
#endif | #endif | ||||
/* | /* | ||||
* Attempt to open the backend device and read the MAC address | * Attempt to open the backend device and read the MAC address | ||||
* if specified. | * if specified. | ||||
*/ | */ | ||||
mac_provided = 0; | mac_provided = 0; | ||||
if (opts != NULL) { | if (opts != NULL) { | ||||
int mrg_rxbuf_enabled = 0; | |||||
int mrg_rxbuf_disabled = 0; | |||||
char *devname; | char *devname; | ||||
char *vtopts; | char *vtopts; | ||||
int err; | int err = 0; | ||||
/* Get the device name. */ | |||||
devname = vtopts = strdup(opts); | devname = vtopts = strdup(opts); | ||||
(void) strsep(&vtopts, ","); | (void) strsep(&vtopts, ","); | ||||
if (vtopts != NULL) { | /* | ||||
err = net_parsemac(vtopts, sc->vsc_config.mac); | * Parse the list of options in the form | ||||
if (err != 0) { | * key1=value1,...,keyN=valueN. | ||||
*/ | |||||
while (vtopts != NULL) { | |||||
char *value = vtopts; | |||||
char *key; | |||||
key = strsep(&value, "="); | |||||
if (value == NULL) | |||||
break; | |||||
vtopts = value; | |||||
(void) strsep(&vtopts, ","); | |||||
if (strcmp(key, "mac") == 0) { | |||||
err = net_parsemac(value, sc->vsc_config.mac); | |||||
if (err) | |||||
break; | |||||
mac_provided = 1; | |||||
} else if (strcmp(key, "mrgrxbuf") == 0) { | |||||
if (strcmp(value, "on") == 0) { | |||||
mrg_rxbuf_enabled = 1; | |||||
mrg_rxbuf_disabled = 0; | |||||
} else { | |||||
mrg_rxbuf_disabled = 1; | |||||
mrg_rxbuf_enabled = 0; | |||||
} | |||||
} | |||||
} | |||||
if (err) { | |||||
free(devname); | free(devname); | ||||
free(sc); | free(sc); | ||||
return (err); | return (err); | ||||
} | } | ||||
mac_provided = 1; | |||||
} | |||||
err = netbe_init(&sc->vsc_be, devname, pci_vtnet_rx_callback, | err = netbe_init(&sc->vsc_be, devname, pci_vtnet_rx_callback, | ||||
sc); | sc); | ||||
free(devname); | free(devname); | ||||
if (err) { | if (err) { | ||||
free(sc); | free(sc); | ||||
return (err); | return (err); | ||||
} | } | ||||
sc->vsc_consts.vc_hv_caps |= netbe_get_cap(sc->vsc_be); | sc->vsc_consts.vc_hv_caps |= netbe_get_cap(sc->vsc_be); | ||||
if (mrg_rxbuf_enabled) | |||||
sc->vsc_consts.vc_hv_caps |= VIRTIO_NET_F_MRG_RXBUF; | |||||
else if (mrg_rxbuf_disabled) | |||||
sc->vsc_consts.vc_hv_caps &= ~VIRTIO_NET_F_MRG_RXBUF; | |||||
} | } | ||||
if (!mac_provided) { | if (!mac_provided) { | ||||
net_genmac(pi, sc->vsc_config.mac); | net_genmac(pi, sc->vsc_config.mac); | ||||
} | } | ||||
/* initialize config space */ | /* initialize config space */ | ||||
pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_NET); | pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_NET); | ||||
Show All 15 Lines | #endif | ||||
} | } | ||||
/* use BAR 0 to map config regs in IO space */ | /* use BAR 0 to map config regs in IO space */ | ||||
vi_set_io_bar(&sc->vsc_vs, 0); | vi_set_io_bar(&sc->vsc_vs, 0); | ||||
sc->resetting = 0; | sc->resetting = 0; | ||||
sc->rx_merge = 0; | sc->rx_merge = 0; | ||||
sc->vhdrlen = sizeof(struct virtio_net_rxhdr) - 2; | |||||
pthread_mutex_init(&sc->rx_mtx, NULL); | pthread_mutex_init(&sc->rx_mtx, NULL); | ||||
/* | /* | ||||
* Initialize tx semaphore & spawn TX processing thread. | * Initialize tx semaphore & spawn TX processing thread. | ||||
* As of now, only one thread for TX desc processing is | * As of now, only one thread for TX desc processing is | ||||
* spawned. | * spawned. | ||||
*/ | */ | ||||
sc->tx_in_progress = 0; | sc->tx_in_progress = 0; | ||||
Show All 38 Lines | pci_vtnet_cfgread(void *vsc, int offset, int size, uint32_t *retval) | ||||
memcpy(retval, ptr, size); | memcpy(retval, ptr, size); | ||||
return (0); | return (0); | ||||
} | } | ||||
static void | static void | ||||
pci_vtnet_neg_features(void *vsc, uint64_t negotiated_features) | pci_vtnet_neg_features(void *vsc, uint64_t negotiated_features) | ||||
{ | { | ||||
struct pci_vtnet_softc *sc = vsc; | struct pci_vtnet_softc *sc = vsc; | ||||
unsigned int rx_vhdrlen; | |||||
sc->vsc_features = negotiated_features; | sc->vsc_features = negotiated_features; | ||||
if (negotiated_features & VIRTIO_NET_F_MRG_RXBUF) { | if (negotiated_features & VIRTIO_NET_F_MRG_RXBUF) { | ||||
rx_vhdrlen = sizeof(struct virtio_net_rxhdr); | sc->vhdrlen = sizeof(struct virtio_net_rxhdr); | ||||
sc->rx_merge = 1; | sc->rx_merge = 1; | ||||
} else { | } else { | ||||
/* | /* | ||||
* Without mergeable rx buffers, virtio-net header is 2 | * Without mergeable rx buffers, virtio-net header is 2 | ||||
* bytes shorter than sizeof(struct virtio_net_rxhdr). | * bytes shorter than sizeof(struct virtio_net_rxhdr). | ||||
*/ | */ | ||||
rx_vhdrlen = sizeof(struct virtio_net_rxhdr) - 2; | sc->vhdrlen = sizeof(struct virtio_net_rxhdr) - 2; | ||||
sc->rx_merge = 0; | sc->rx_merge = 0; | ||||
} | } | ||||
/* Tell the backend to enable some capabilities it has advertised. */ | /* Tell the backend to enable some capabilities it has advertised. */ | ||||
netbe_set_cap(sc->vsc_be, negotiated_features, rx_vhdrlen); | netbe_set_cap(sc->vsc_be, negotiated_features, sc->vhdrlen); | ||||
sc->be_vhdrlen = netbe_get_vnet_hdr_len(sc->vsc_be); | |||||
assert(sc->be_vhdrlen == 0 || sc->be_vhdrlen == sc->vhdrlen); | |||||
} | } | ||||
static struct pci_devemu pci_de_vnet = { | static struct pci_devemu pci_de_vnet = { | ||||
.pe_emu = "virtio-net", | .pe_emu = "virtio-net", | ||||
.pe_init = pci_vtnet_init, | .pe_init = pci_vtnet_init, | ||||
.pe_barwrite = vi_pci_write, | .pe_barwrite = vi_pci_write, | ||||
.pe_barread = vi_pci_read | .pe_barread = vi_pci_read | ||||
}; | }; | ||||
PCI_EMUL_SET(pci_de_vnet); | PCI_EMUL_SET(pci_de_vnet); |
We need to initialize 'struct virtio_net_rxhdr *hdr = iov[0].iov_base;' before calling netbe_recv() which can change value of iov[0].iov_base.