#include <sys/cdefs.h>
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <machine/in_cksum.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <machine/_inttypes.h>
#include <xen/xen-os.h>
#include <xen/hypervisor.h>
#include <xen/xen_intr.h>
#include <contrib/xen/io/netif.h>
#include <xen/xenbus/xenbusvar.h>
static MALLOC_DEFINE(M_XENNETBACK, "xnb", "Xen Net Back Driver Data");
#define XNB_SG 1
#define XNB_GSO_TCPV4 0
#define XNB_RX_COPY 1
#define XNB_RX_FLIP 0
#undef XNB_DEBUG
#define XNB_DEBUG
#ifdef XNB_DEBUG
#define DPRINTF(fmt, args...) \
printf("xnb(%s:%d): " fmt, __FUNCTION__, __LINE__, ##args)
#else
#define DPRINTF(fmt, args...) do {} while (0)
#endif
#define GNTTAB_LEN (64)
#define XNB_CSUM_FEATURES (CSUM_TCP | CSUM_UDP)
#define NET_TX_RING_SIZE __RING_SIZE((netif_tx_sring_t *)0, PAGE_SIZE)
#define NET_RX_RING_SIZE __RING_SIZE((netif_rx_sring_t *)0, PAGE_SIZE)
#define RING_HAS_UNCONSUMED_REQUESTS_2(_r, cons) ({ \
unsigned int req = (_r)->sring->req_prod - cons; \
unsigned int rsp = RING_SIZE(_r) - \
(cons - (_r)->rsp_prod_pvt); \
req < rsp ? req : rsp; \
})
#define virt_to_mfn(x) (vtophys(x) >> PAGE_SHIFT)
#define virt_to_offset(x) ((x) & (PAGE_SIZE - 1))
typedef struct gnttab_copy gnttab_copy_table[GNTTAB_LEN];
struct xnb_softc;
struct xnb_pkt;
static void xnb_attach_failed(struct xnb_softc *xnb,
int err, const char *fmt, ...)
__printflike(3,4);
static int xnb_shutdown(struct xnb_softc *xnb);
static int create_netdev(device_t dev);
static int xnb_detach(device_t dev);
static int xnb_ifmedia_upd(if_t ifp);
static void xnb_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr);
static void xnb_intr(void *arg);
static int xnb_send(netif_rx_back_ring_t *rxb, domid_t otherend,
const struct mbuf *mbufc, gnttab_copy_table gnttab);
static int xnb_recv(netif_tx_back_ring_t *txb, domid_t otherend,
struct mbuf **mbufc, if_t ifnet,
gnttab_copy_table gnttab);
static int xnb_ring2pkt(struct xnb_pkt *pkt,
const netif_tx_back_ring_t *tx_ring,
RING_IDX start);
static void xnb_txpkt2rsp(const struct xnb_pkt *pkt,
netif_tx_back_ring_t *ring, int error);
static struct mbuf *xnb_pkt2mbufc(const struct xnb_pkt *pkt, if_t ifp);
static int xnb_txpkt2gnttab(const struct xnb_pkt *pkt,
struct mbuf *mbufc,
gnttab_copy_table gnttab,
const netif_tx_back_ring_t *txb,
domid_t otherend_id);
static void xnb_update_mbufc(struct mbuf *mbufc,
const gnttab_copy_table gnttab, int n_entries);
static int xnb_mbufc2pkt(const struct mbuf *mbufc,
struct xnb_pkt *pkt,
RING_IDX start, int space);
static int xnb_rxpkt2gnttab(const struct xnb_pkt *pkt,
const struct mbuf *mbufc,
gnttab_copy_table gnttab,
const netif_rx_back_ring_t *rxb,
domid_t otherend_id);
static int xnb_rxpkt2rsp(const struct xnb_pkt *pkt,
const gnttab_copy_table gnttab, int n_entries,
netif_rx_back_ring_t *ring);
static void xnb_stop(struct xnb_softc*);
static int xnb_ioctl(if_t, u_long, caddr_t);
static void xnb_start_locked(if_t);
static void xnb_start(if_t);
static void xnb_ifinit_locked(struct xnb_softc*);
static void xnb_ifinit(void*);
#ifdef XNB_DEBUG
static int xnb_unit_test_main(SYSCTL_HANDLER_ARGS);
static int xnb_dump_rings(SYSCTL_HANDLER_ARGS);
#endif
#if defined(INET) || defined(INET6)
static void xnb_add_mbuf_cksum(struct mbuf *mbufc);
#endif
struct xnb_pkt{
RING_IDX car;
RING_IDX cdr;
netif_extra_info_t extra;
uint16_t size;
uint16_t car_size;
uint16_t flags;
uint16_t list_len;
uint8_t error;
};
static inline void
xnb_pkt_initialize(struct xnb_pkt *pxnb)
{
bzero(pxnb, sizeof(*pxnb));
}
static inline void
xnb_pkt_validate(struct xnb_pkt *pxnb)
{
pxnb->error = 0;
};
static inline void
xnb_pkt_invalidate(struct xnb_pkt *pxnb)
{
pxnb->error = 1;
};
static inline int
xnb_pkt_is_valid(const struct xnb_pkt *pxnb)
{
return (! pxnb->error);
}
#ifdef XNB_DEBUG
static void __unused
xnb_dump_pkt(const struct xnb_pkt *pkt) {
if (pkt == NULL) {
DPRINTF("Was passed a null pointer.\n");
return;
}
DPRINTF("pkt address= %p\n", pkt);
DPRINTF("pkt->size=%d\n", pkt->size);
DPRINTF("pkt->car_size=%d\n", pkt->car_size);
DPRINTF("pkt->flags=0x%04x\n", pkt->flags);
DPRINTF("pkt->list_len=%d\n", pkt->list_len);
DPRINTF("pkt->car=%d\n", pkt->car);
DPRINTF("pkt->cdr=%d\n", pkt->cdr);
DPRINTF("pkt->error=%d\n", pkt->error);
}
#endif
static void
xnb_dump_txreq(RING_IDX idx, const struct netif_tx_request *txreq)
{
if (txreq != NULL) {
DPRINTF("netif_tx_request index =%u\n", idx);
DPRINTF("netif_tx_request.gref =%u\n", txreq->gref);
DPRINTF("netif_tx_request.offset=%hu\n", txreq->offset);
DPRINTF("netif_tx_request.flags =%hu\n", txreq->flags);
DPRINTF("netif_tx_request.id =%hu\n", txreq->id);
DPRINTF("netif_tx_request.size =%hu\n", txreq->size);
}
}
struct xnb_ring_config {
union{
netif_rx_back_ring_t rx_ring;
netif_tx_back_ring_t tx_ring;
} back_ring;
uint64_t bus_addr;
uint64_t gnt_addr;
vm_offset_t va;
grant_handle_t handle;
unsigned ring_pages;
grant_ref_t ring_ref;
};
typedef enum
{
XNBF_RING_CONNECTED = 0x01,
XNBF_RESOURCE_SHORTAGE = 0x02,
XNBF_SHUTDOWN = 0x04,
XNBF_IN_SHUTDOWN = 0x08
} xnb_flag_t;
typedef enum{
XNB_RING_TYPE_TX = 0,
XNB_RING_TYPE_RX = 1,
XNB_NUM_RING_TYPES
} xnb_ring_type_t;
struct xnb_softc {
device_t dev;
struct ifmedia sc_media;
if_t xnb_ifp;
unsigned carrier;
uint8_t mac[ETHER_ADDR_LEN];
int abi;
char *bridge;
evtchn_port_t evtchn;
long handle;
xen_intr_handle_t xen_intr_handle;
domid_t otherend_id;
uint8_t can_sg;
uint8_t gso;
uint8_t gso_prefix;
uint8_t ip_csum;
gnttab_copy_table rx_gnttab;
gnttab_copy_table tx_gnttab;
struct resource *pseudo_phys_res;
int pseudo_phys_res_id;
struct xnb_ring_config ring_configs[XNB_NUM_RING_TYPES];
vm_offset_t kva;
uint64_t gnt_base_addr;
xnb_flag_t flags;
struct mtx rx_lock;
struct mtx sc_lock;
struct mtx tx_lock;
int kva_size;
char if_name[IFNAMSIZ];
};
#ifdef XNB_DEBUG
static void __unused
xnb_dump_gnttab_copy(const struct gnttab_copy *entry)
{
if (entry == NULL) {
printf("NULL grant table pointer\n");
return;
}
if (entry->flags & GNTCOPY_dest_gref)
printf("gnttab dest ref=\t%u\n", entry->dest.u.ref);
else
printf("gnttab dest gmfn=\t%"PRI_xen_pfn"\n",
entry->dest.u.gmfn);
printf("gnttab dest offset=\t%hu\n", entry->dest.offset);
printf("gnttab dest domid=\t%hu\n", entry->dest.domid);
if (entry->flags & GNTCOPY_source_gref)
printf("gnttab source ref=\t%u\n", entry->source.u.ref);
else
printf("gnttab source gmfn=\t%"PRI_xen_pfn"\n",
entry->source.u.gmfn);
printf("gnttab source offset=\t%hu\n", entry->source.offset);
printf("gnttab source domid=\t%hu\n", entry->source.domid);
printf("gnttab len=\t%hu\n", entry->len);
printf("gnttab flags=\t%hu\n", entry->flags);
printf("gnttab status=\t%hd\n", entry->status);
}
static int
xnb_dump_rings(SYSCTL_HANDLER_ARGS)
{
static char results[720];
struct xnb_softc const* xnb = (struct xnb_softc*)arg1;
netif_rx_back_ring_t const* rxb =
&xnb->ring_configs[XNB_RING_TYPE_RX].back_ring.rx_ring;
netif_tx_back_ring_t const* txb =
&xnb->ring_configs[XNB_RING_TYPE_TX].back_ring.tx_ring;
results[0] = 0;
if ( !txb || !txb->sring || !rxb || !rxb->sring )
return (SYSCTL_OUT(req, results, strnlen(results, 720)));
snprintf(results, 720,
"\n\t%35s %18s\n"
"\t%16s %18d %18d\n"
"\t%16s %18d %18d\n"
"\t%16s %18d %18d\n"
"\t%16s %18p %18p\n"
"\t%16s %18d %18d\n"
"\t%16s %18d %18d\n"
"\t%16s %18d %18d\n"
"\t%16s %18d %18d\n",
"TX", "RX",
"req_cons", txb->req_cons, rxb->req_cons,
"nr_ents", txb->nr_ents, rxb->nr_ents,
"rsp_prod_pvt", txb->rsp_prod_pvt, rxb->rsp_prod_pvt,
"sring", txb->sring, rxb->sring,
"sring->req_prod", txb->sring->req_prod, rxb->sring->req_prod,
"sring->req_event", txb->sring->req_event, rxb->sring->req_event,
"sring->rsp_prod", txb->sring->rsp_prod, rxb->sring->rsp_prod,
"sring->rsp_event", txb->sring->rsp_event, rxb->sring->rsp_event);
return (SYSCTL_OUT(req, results, strnlen(results, 720)));
}
static void __unused
xnb_dump_mbuf(const struct mbuf *m)
{
int len;
uint8_t *d;
if (m == NULL)
return;
printf("xnb_dump_mbuf:\n");
if (m->m_flags & M_PKTHDR) {
printf(" flowid=%10d, csum_flags=%#8x, csum_data=%#8x, "
"tso_segsz=%5hd\n",
m->m_pkthdr.flowid, (int)m->m_pkthdr.csum_flags,
m->m_pkthdr.csum_data, m->m_pkthdr.tso_segsz);
printf(" rcvif=%16p, len=%19d\n",
m->m_pkthdr.rcvif, m->m_pkthdr.len);
}
printf(" m_next=%16p, m_nextpk=%16p, m_data=%16p\n",
m->m_next, m->m_nextpkt, m->m_data);
printf(" m_len=%17d, m_flags=%#15x, m_type=%18u\n",
m->m_len, m->m_flags, m->m_type);
len = m->m_len;
d = mtod(m, uint8_t*);
while (len > 0) {
int i;
printf(" ");
for (i = 0; (i < 16) && (len > 0); i++, len--) {
printf("%02hhx ", *(d++));
}
printf("\n");
}
}
#endif
static void
xnb_free_communication_mem(struct xnb_softc *xnb)
{
if (xnb->kva != 0) {
if (xnb->pseudo_phys_res != NULL) {
xenmem_free(xnb->dev, xnb->pseudo_phys_res_id,
xnb->pseudo_phys_res);
xnb->pseudo_phys_res = NULL;
}
}
xnb->kva = 0;
xnb->gnt_base_addr = 0;
}
static int
xnb_disconnect(struct xnb_softc *xnb)
{
struct gnttab_unmap_grant_ref gnts[XNB_NUM_RING_TYPES];
int error __diagused;
int i;
if (xnb->xen_intr_handle != NULL)
xen_intr_unbind(&xnb->xen_intr_handle);
mtx_lock(&xnb->tx_lock);
mtx_unlock(&xnb->tx_lock);
mtx_lock(&xnb->rx_lock);
mtx_unlock(&xnb->rx_lock);
mtx_lock(&xnb->sc_lock);
if (xnb->bridge != NULL) {
free(xnb->bridge, M_XENSTORE);
xnb->bridge = NULL;
}
for (i=0; i < XNB_NUM_RING_TYPES; i++) {
gnts[i].host_addr = xnb->ring_configs[i].gnt_addr;
gnts[i].dev_bus_addr = xnb->ring_configs[i].bus_addr;
gnts[i].handle = xnb->ring_configs[i].handle;
}
error = HYPERVISOR_grant_table_op(GNTTABOP_unmap_grant_ref, gnts,
XNB_NUM_RING_TYPES);
KASSERT(error == 0, ("Grant table unmap op failed (%d)", error));
xnb_free_communication_mem(xnb);
bzero(&xnb->ring_configs[XNB_RING_TYPE_TX],
sizeof(struct xnb_ring_config));
bzero(&xnb->ring_configs[XNB_RING_TYPE_RX],
sizeof(struct xnb_ring_config));
xnb->flags &= ~XNBF_RING_CONNECTED;
mtx_unlock(&xnb->sc_lock);
return (0);
}
static int
xnb_connect_ring(struct xnb_softc *xnb, xnb_ring_type_t ring_type)
{
struct gnttab_map_grant_ref gnt;
struct xnb_ring_config *ring = &xnb->ring_configs[ring_type];
int error;
ring->va = xnb->kva + ring_type * PAGE_SIZE;
ring->gnt_addr = xnb->gnt_base_addr + ring_type * PAGE_SIZE;
gnt.host_addr = ring->gnt_addr;
gnt.flags = GNTMAP_host_map;
gnt.ref = ring->ring_ref;
gnt.dom = xnb->otherend_id;
error = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, &gnt, 1);
if (error != 0)
panic("netback: Ring page grant table op failed (%d)", error);
if (gnt.status != 0) {
ring->va = 0;
error = EACCES;
xenbus_dev_fatal(xnb->dev, error,
"Ring shared page mapping failed. "
"Status %d.", gnt.status);
} else {
ring->handle = gnt.handle;
ring->bus_addr = gnt.dev_bus_addr;
if (ring_type == XNB_RING_TYPE_TX) {
BACK_RING_INIT(&ring->back_ring.tx_ring,
(netif_tx_sring_t*)ring->va,
ring->ring_pages * PAGE_SIZE);
} else if (ring_type == XNB_RING_TYPE_RX) {
BACK_RING_INIT(&ring->back_ring.rx_ring,
(netif_rx_sring_t*)ring->va,
ring->ring_pages * PAGE_SIZE);
} else {
xenbus_dev_fatal(xnb->dev, error,
"Unknown ring type %d", ring_type);
}
}
return error;
}
static int
xnb_connect_comms(struct xnb_softc *xnb)
{
int error;
xnb_ring_type_t i;
if ((xnb->flags & XNBF_RING_CONNECTED) != 0)
return (0);
for (i=0; i < XNB_NUM_RING_TYPES; i++) {
error = xnb_connect_ring(xnb, i);
if (error != 0)
return error;
}
xnb->flags |= XNBF_RING_CONNECTED;
error = xen_intr_bind_remote_port(xnb->dev,
xnb->otherend_id,
xnb->evtchn,
NULL,
xnb_intr, xnb,
INTR_TYPE_NET | INTR_MPSAFE,
&xnb->xen_intr_handle);
if (error != 0) {
(void)xnb_disconnect(xnb);
xenbus_dev_fatal(xnb->dev, error, "binding event channel");
return (error);
}
DPRINTF("rings connected!\n");
return (0);
}
static int
xnb_alloc_communication_mem(struct xnb_softc *xnb)
{
xnb_ring_type_t i;
xnb->kva_size = 0;
for (i=0; i < XNB_NUM_RING_TYPES; i++) {
xnb->kva_size += xnb->ring_configs[i].ring_pages * PAGE_SIZE;
}
xnb->pseudo_phys_res_id = 0;
xnb->pseudo_phys_res = xenmem_alloc(xnb->dev, &xnb->pseudo_phys_res_id,
xnb->kva_size);
if (xnb->pseudo_phys_res == NULL) {
xnb->kva = 0;
return (ENOMEM);
}
xnb->kva = (vm_offset_t)rman_get_virtual(xnb->pseudo_phys_res);
xnb->gnt_base_addr = rman_get_start(xnb->pseudo_phys_res);
return (0);
}
static int
xnb_collect_xenstore_info(struct xnb_softc *xnb)
{
const char *otherend_path;
const char *our_path;
int err;
unsigned int rx_copy, bridge_len;
uint8_t no_csum_offload;
otherend_path = xenbus_get_otherend_path(xnb->dev);
our_path = xenbus_get_node(xnb->dev);
err = xs_gather(XST_NIL, otherend_path,
"tx-ring-ref", "%l" PRIu32,
&xnb->ring_configs[XNB_RING_TYPE_TX].ring_ref,
"rx-ring-ref", "%l" PRIu32,
&xnb->ring_configs[XNB_RING_TYPE_RX].ring_ref,
"event-channel", "%" PRIu32, &xnb->evtchn,
NULL);
if (err != 0) {
xenbus_dev_fatal(xnb->dev, err,
"Unable to retrieve ring information from "
"frontend %s. Unable to connect.",
otherend_path);
return (err);
}
err = xs_scanf(XST_NIL, our_path, "handle", NULL, "%li", &xnb->handle);
if (err != 0) {
xenbus_dev_fatal(xnb->dev, err,
"Error reading handle from frontend %s. "
"Unable to connect.", otherend_path);
}
err = xs_read(XST_NIL, our_path, "bridge", &bridge_len,
(void**)&xnb->bridge);
if (err != 0)
xnb->bridge = NULL;
err = xs_scanf(XST_NIL, otherend_path, "request-rx-copy", NULL,
"%" PRIu32, &rx_copy);
if (err == ENOENT) {
err = 0;
rx_copy = 0;
}
if (err < 0) {
xenbus_dev_fatal(xnb->dev, err, "reading %s/request-rx-copy",
otherend_path);
return err;
}
if (xs_scanf(XST_NIL, otherend_path, "feature-sg", NULL,
"%hhu", &xnb->can_sg) < 0)
xnb->can_sg = 0;
if (xs_scanf(XST_NIL, otherend_path, "feature-gso-tcpv4", NULL,
"%hhu", &xnb->gso) < 0)
xnb->gso = 0;
if (xs_scanf(XST_NIL, otherend_path, "feature-gso-tcpv4-prefix", NULL,
"%hhu", &xnb->gso_prefix) < 0)
xnb->gso_prefix = 0;
if (xs_scanf(XST_NIL, otherend_path, "feature-no-csum-offload", NULL,
"%hhu", &no_csum_offload) < 0)
no_csum_offload = 0;
xnb->ip_csum = (no_csum_offload == 0);
return (0);
}
static int
xnb_publish_backend_info(struct xnb_softc *xnb)
{
struct xs_transaction xst;
const char *our_path;
int error;
our_path = xenbus_get_node(xnb->dev);
do {
error = xs_transaction_start(&xst);
if (error != 0) {
xenbus_dev_fatal(xnb->dev, error,
"Error publishing backend info "
"(start transaction)");
break;
}
error = xs_printf(xst, our_path, "feature-sg",
"%d", XNB_SG);
if (error != 0)
break;
error = xs_printf(xst, our_path, "feature-gso-tcpv4",
"%d", XNB_GSO_TCPV4);
if (error != 0)
break;
error = xs_printf(xst, our_path, "feature-rx-copy",
"%d", XNB_RX_COPY);
if (error != 0)
break;
error = xs_printf(xst, our_path, "feature-rx-flip",
"%d", XNB_RX_FLIP);
if (error != 0)
break;
error = xs_transaction_end(xst, 0);
if (error != 0 && error != EAGAIN) {
xenbus_dev_fatal(xnb->dev, error, "ending transaction");
break;
}
} while (error == EAGAIN);
return (error);
}
static void
xnb_connect(struct xnb_softc *xnb)
{
int error;
if (xenbus_get_state(xnb->dev) == XenbusStateConnected)
return;
if (xnb_collect_xenstore_info(xnb) != 0)
return;
xnb->flags &= ~XNBF_SHUTDOWN;
error = xnb_alloc_communication_mem(xnb);
if (error != 0) {
xenbus_dev_fatal(xnb->dev, error,
"Unable to allocate communication memory");
return;
}
error = xnb_connect_comms(xnb);
if (error != 0) {
return;
}
xnb->carrier = 1;
xenbus_set_state(xnb->dev, XenbusStateConnected);
}
static int
xnb_shutdown(struct xnb_softc *xnb)
{
if ((xnb->flags & XNBF_IN_SHUTDOWN) != 0)
return (EAGAIN);
xnb->flags |= XNBF_SHUTDOWN;
xnb->flags |= XNBF_IN_SHUTDOWN;
mtx_unlock(&xnb->sc_lock);
xnb->carrier = 0;
if (xnb->xnb_ifp != NULL) {
ether_ifdetach(xnb->xnb_ifp);
if_free(xnb->xnb_ifp);
xnb->xnb_ifp = NULL;
}
xnb_disconnect(xnb);
if (xenbus_get_state(xnb->dev) < XenbusStateClosing)
xenbus_set_state(xnb->dev, XenbusStateClosing);
mtx_lock(&xnb->sc_lock);
xnb->flags &= ~XNBF_IN_SHUTDOWN;
wakeup(xnb);
return (0);
}
static void
xnb_attach_failed(struct xnb_softc *xnb, int err, const char *fmt, ...)
{
va_list ap;
va_list ap_hotplug;
va_start(ap, fmt);
va_copy(ap_hotplug, ap);
xs_vprintf(XST_NIL, xenbus_get_node(xnb->dev),
"hotplug-error", fmt, ap_hotplug);
va_end(ap_hotplug);
(void)xs_printf(XST_NIL, xenbus_get_node(xnb->dev),
"hotplug-status", "error");
xenbus_dev_vfatal(xnb->dev, err, fmt, ap);
va_end(ap);
(void)xs_printf(XST_NIL, xenbus_get_node(xnb->dev), "online", "0");
xnb_detach(xnb->dev);
}
static int
xnb_probe(device_t dev)
{
if (!strcmp(xenbus_get_type(dev), "vif")) {
DPRINTF("Claiming device %d, %s\n", device_get_unit(dev),
devclass_get_name(device_get_devclass(dev)));
device_set_desc(dev, "Backend Virtual Network Device");
device_quiet(dev);
return (0);
}
return (ENXIO);
}
static void
xnb_setup_sysctl(struct xnb_softc *xnb)
{
struct sysctl_ctx_list *sysctl_ctx = NULL;
struct sysctl_oid *sysctl_tree = NULL;
sysctl_ctx = device_get_sysctl_ctx(xnb->dev);
if (sysctl_ctx == NULL)
return;
sysctl_tree = device_get_sysctl_tree(xnb->dev);
if (sysctl_tree == NULL)
return;
#ifdef XNB_DEBUG
SYSCTL_ADD_PROC(sysctl_ctx,
SYSCTL_CHILDREN(sysctl_tree),
OID_AUTO,
"unit_test_results",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
xnb,
0,
xnb_unit_test_main,
"A",
"Results of builtin unit tests");
SYSCTL_ADD_PROC(sysctl_ctx,
SYSCTL_CHILDREN(sysctl_tree),
OID_AUTO,
"dump_rings",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
xnb,
0,
xnb_dump_rings,
"A",
"Xennet Back Rings");
#endif
}
int
create_netdev(device_t dev)
{
if_t ifp;
struct xnb_softc *xnb;
int err = 0;
uint32_t handle;
xnb = device_get_softc(dev);
mtx_init(&xnb->sc_lock, "xnb_softc", "xen netback softc lock", MTX_DEF);
mtx_init(&xnb->tx_lock, "xnb_tx", "xen netback tx lock", MTX_DEF);
mtx_init(&xnb->rx_lock, "xnb_rx", "xen netback rx lock", MTX_DEF);
xnb->dev = dev;
ifmedia_init(&xnb->sc_media, 0, xnb_ifmedia_upd, xnb_ifmedia_sts);
ifmedia_add(&xnb->sc_media, IFM_ETHER|IFM_MANUAL, 0, NULL);
ifmedia_set(&xnb->sc_media, IFM_ETHER|IFM_MANUAL);
bzero(&xnb->mac[0], sizeof(xnb->mac));
err = xs_scanf(XST_NIL, xenbus_get_node(xnb->dev), "handle", NULL,
"%" PRIu32, &handle);
if (err != 0)
return (err);
snprintf(xnb->if_name, IFNAMSIZ, "xnb%" PRIu16 ".%" PRIu32,
xenbus_get_otherend_id(dev), handle);
if (err == 0) {
ifp = xnb->xnb_ifp = if_alloc(IFT_ETHER);
if_setsoftc(ifp, xnb);
if_initname(ifp, xnb->if_name, IF_DUNIT_NONE);
if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
if_setioctlfn(ifp, xnb_ioctl);
if_setstartfn(ifp, xnb_start);
if_setinitfn(ifp, xnb_ifinit);
if_setmtu(ifp, ETHERMTU);
if_setsendqlen(ifp, NET_RX_RING_SIZE - 1);
if_sethwassist(ifp, XNB_CSUM_FEATURES);
if_setcapabilities(ifp, IFCAP_HWCSUM);
if_setcapenable(ifp, IFCAP_HWCSUM);
ether_ifattach(ifp, xnb->mac);
xnb->carrier = 0;
}
return err;
}
static int
xnb_attach(device_t dev)
{
struct xnb_softc *xnb;
int error;
xnb_ring_type_t i;
error = create_netdev(dev);
if (error != 0) {
xenbus_dev_fatal(dev, error, "creating netdev");
return (error);
}
DPRINTF("Attaching to %s\n", xenbus_get_node(dev));
xnb = device_get_softc(dev);
xnb->otherend_id = xenbus_get_otherend_id(dev);
for (i=0; i < XNB_NUM_RING_TYPES; i++) {
xnb->ring_configs[i].ring_pages = 1;
}
xnb_setup_sysctl(xnb);
error = xs_printf(XST_NIL, xenbus_get_node(xnb->dev),
"hotplug-status", "connected");
if (error != 0) {
xnb_attach_failed(xnb, error, "writing %s/hotplug-status",
xenbus_get_node(xnb->dev));
return (error);
}
if ((error = xnb_publish_backend_info(xnb)) != 0) {
xnb_attach_failed(xnb, error,
"Publishing backend status for %s",
xenbus_get_node(xnb->dev));
return error;
}
xenbus_set_state(dev, XenbusStateInitWait);
return (0);
}
static int
xnb_detach(device_t dev)
{
struct xnb_softc *xnb;
DPRINTF("\n");
xnb = device_get_softc(dev);
mtx_lock(&xnb->sc_lock);
while (xnb_shutdown(xnb) == EAGAIN) {
msleep(xnb, &xnb->sc_lock, 0,
"xnb_shutdown", 0);
}
mtx_unlock(&xnb->sc_lock);
DPRINTF("\n");
mtx_destroy(&xnb->tx_lock);
mtx_destroy(&xnb->rx_lock);
mtx_destroy(&xnb->sc_lock);
return (0);
}
static int
xnb_suspend(device_t dev)
{
return (0);
}
static int
xnb_resume(device_t dev)
{
return (0);
}
static void
xnb_frontend_changed(device_t dev, XenbusState frontend_state)
{
struct xnb_softc *xnb;
xnb = device_get_softc(dev);
DPRINTF("frontend_state=%s, xnb_state=%s\n",
xenbus_strstate(frontend_state),
xenbus_strstate(xenbus_get_state(xnb->dev)));
switch (frontend_state) {
case XenbusStateInitialising:
case XenbusStateInitialised:
break;
case XenbusStateConnected:
xnb_connect(xnb);
break;
case XenbusStateClosing:
case XenbusStateClosed:
mtx_lock(&xnb->sc_lock);
xnb_shutdown(xnb);
mtx_unlock(&xnb->sc_lock);
if (frontend_state == XenbusStateClosed)
xenbus_set_state(xnb->dev, XenbusStateClosed);
break;
default:
xenbus_dev_fatal(xnb->dev, EINVAL, "saw state %d at frontend",
frontend_state);
break;
}
}
static void
xnb_intr(void *arg)
{
struct xnb_softc *xnb;
if_t ifp;
netif_tx_back_ring_t *txb;
RING_IDX req_prod_local;
xnb = (struct xnb_softc *)arg;
ifp = xnb->xnb_ifp;
txb = &xnb->ring_configs[XNB_RING_TYPE_TX].back_ring.tx_ring;
mtx_lock(&xnb->tx_lock);
do {
int notify;
req_prod_local = txb->sring->req_prod;
xen_rmb();
for (;;) {
struct mbuf *mbufc;
int err;
err = xnb_recv(txb, xnb->otherend_id, &mbufc, ifp,
xnb->tx_gnttab);
if (err || (mbufc == NULL))
break;
if_input(xnb->xnb_ifp, mbufc);
}
RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(txb, notify);
if (notify != 0)
xen_intr_signal(xnb->xen_intr_handle);
txb->sring->req_event = txb->req_cons + 1;
xen_mb();
} while (txb->sring->req_prod != req_prod_local) ;
mtx_unlock(&xnb->tx_lock);
xnb_start(ifp);
}
static int
xnb_ring2pkt(struct xnb_pkt *pkt, const netif_tx_back_ring_t *tx_ring,
RING_IDX start)
{
int idx = start;
int discard = 0;
int more_data = 0;
uint16_t cdr_size = 0;
xnb_pkt_initialize(pkt);
if (RING_HAS_UNCONSUMED_REQUESTS_2(tx_ring, idx)) {
netif_tx_request_t *tx = RING_GET_REQUEST(tx_ring, idx);
pkt->size = tx->size;
pkt->flags = tx->flags & ~NETTXF_more_data;
more_data = tx->flags & NETTXF_more_data;
pkt->list_len++;
pkt->car = idx;
idx++;
}
if ((pkt->flags & NETTXF_extra_info) &&
RING_HAS_UNCONSUMED_REQUESTS_2(tx_ring, idx)) {
netif_extra_info_t *ext =
(netif_extra_info_t*) RING_GET_REQUEST(tx_ring, idx);
pkt->extra.type = ext->type;
switch (pkt->extra.type) {
case XEN_NETIF_EXTRA_TYPE_GSO:
pkt->extra.u.gso = ext->u.gso;
break;
default:
printf("xnb(%s:%d): Unknown extra info type %d."
" Discarding packet\n",
__func__, __LINE__, pkt->extra.type);
xnb_dump_txreq(start, RING_GET_REQUEST(tx_ring,
start));
xnb_dump_txreq(idx, RING_GET_REQUEST(tx_ring,
idx));
discard = 1;
break;
}
pkt->extra.flags = ext->flags;
if (ext->flags & XEN_NETIF_EXTRA_FLAG_MORE) {
printf("xnb(%s:%d): Request sets "
"XEN_NETIF_EXTRA_FLAG_MORE, but we can't handle "
"that\n", __func__, __LINE__);
xnb_dump_txreq(start, RING_GET_REQUEST(tx_ring, start));
xnb_dump_txreq(idx, RING_GET_REQUEST(tx_ring, idx));
discard = 1;
}
idx++;
}
pkt->cdr = idx;
while (more_data && RING_HAS_UNCONSUMED_REQUESTS_2(tx_ring, idx)) {
netif_tx_request_t *tx = RING_GET_REQUEST(tx_ring, idx);
pkt->list_len++;
cdr_size += tx->size;
if (tx->flags & ~NETTXF_more_data) {
printf("xnb(%s:%d): Request sets unknown flags %d "
"after the 1st request in the packet.\n",
__func__, __LINE__, tx->flags);
xnb_dump_txreq(start, RING_GET_REQUEST(tx_ring, start));
xnb_dump_txreq(idx, RING_GET_REQUEST(tx_ring, idx));
}
more_data = tx->flags & NETTXF_more_data;
idx++;
}
if (more_data != 0) {
xnb_pkt_invalidate(pkt);
idx = start;
} else {
pkt->car_size = pkt->size - cdr_size;
}
if (discard != 0) {
xnb_pkt_invalidate(pkt);
}
return idx - start;
}
static void
xnb_txpkt2rsp(const struct xnb_pkt *pkt, netif_tx_back_ring_t *ring,
int error)
{
netif_tx_request_t *tx;
netif_tx_response_t *rsp;
int i;
uint16_t status;
status = (xnb_pkt_is_valid(pkt) == 0) || error ?
NETIF_RSP_ERROR : NETIF_RSP_OKAY;
KASSERT((pkt->list_len == 0) || (ring->rsp_prod_pvt == pkt->car),
("Cannot respond to ring requests out of order"));
if (pkt->list_len >= 1) {
uint16_t id;
tx = RING_GET_REQUEST(ring, ring->rsp_prod_pvt);
id = tx->id;
rsp = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt);
rsp->id = id;
rsp->status = status;
ring->rsp_prod_pvt++;
if (pkt->flags & NETRXF_extra_info) {
rsp = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt);
rsp->status = NETIF_RSP_NULL;
ring->rsp_prod_pvt++;
}
}
for (i=0; i < pkt->list_len - 1; i++) {
uint16_t id;
tx = RING_GET_REQUEST(ring, ring->rsp_prod_pvt);
id = tx->id;
rsp = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt);
rsp->id = id;
rsp->status = status;
ring->rsp_prod_pvt++;
}
}
static struct mbuf*
xnb_pkt2mbufc(const struct xnb_pkt *pkt, if_t ifp)
{
struct mbuf *m;
m = m_getm(NULL, pkt->size, M_NOWAIT, MT_DATA);
if (m != NULL) {
m->m_pkthdr.rcvif = ifp;
if (pkt->flags & NETTXF_data_validated) {
m->m_pkthdr.csum_flags = (
CSUM_IP_CHECKED |
CSUM_IP_VALID |
CSUM_DATA_VALID |
CSUM_PSEUDO_HDR
);
m->m_pkthdr.csum_data = 0xffff;
}
}
return m;
}
static int
xnb_txpkt2gnttab(const struct xnb_pkt *pkt, struct mbuf *mbufc,
gnttab_copy_table gnttab, const netif_tx_back_ring_t *txb,
domid_t otherend_id)
{
struct mbuf *mbuf = mbufc;
int gnt_idx = 0;
RING_IDX r_idx = pkt->car;
int r_ofs = 0;
int m_ofs = 0;
uint16_t size_remaining = pkt->size;
while (size_remaining > 0) {
const netif_tx_request_t *txq = RING_GET_REQUEST(txb, r_idx);
const size_t mbuf_space = M_TRAILINGSPACE(mbuf) - m_ofs;
const size_t req_size =
r_idx == pkt->car ? pkt->car_size : txq->size;
const size_t pkt_space = req_size - r_ofs;
const size_t space = MIN(pkt_space, mbuf_space);
KASSERT(gnt_idx < GNTTAB_LEN, ("Grant table is too short"));
gnttab[gnt_idx].source.u.ref = txq->gref;
gnttab[gnt_idx].source.domid = otherend_id;
gnttab[gnt_idx].source.offset = txq->offset + r_ofs;
gnttab[gnt_idx].dest.u.gmfn = virt_to_mfn(
mtod(mbuf, vm_offset_t) + m_ofs);
gnttab[gnt_idx].dest.offset = virt_to_offset(
mtod(mbuf, vm_offset_t) + m_ofs);
gnttab[gnt_idx].dest.domid = DOMID_SELF;
gnttab[gnt_idx].len = space;
gnttab[gnt_idx].flags = GNTCOPY_source_gref;
gnt_idx++;
r_ofs += space;
m_ofs += space;
size_remaining -= space;
if (req_size - r_ofs <= 0) {
r_ofs = 0;
r_idx = (r_idx == pkt->car) ? pkt->cdr : r_idx + 1;
}
if (M_TRAILINGSPACE(mbuf) - m_ofs <= 0) {
m_ofs = 0;
mbuf = mbuf->m_next;
}
}
return gnt_idx;
}
static void
xnb_update_mbufc(struct mbuf *mbufc, const gnttab_copy_table gnttab,
int n_entries)
{
struct mbuf *mbuf = mbufc;
int i;
size_t total_size = 0;
for (i = 0; i < n_entries; i++) {
KASSERT(gnttab[i].status == GNTST_okay,
("Some gnttab_copy entry had error status %hd\n",
gnttab[i].status));
mbuf->m_len += gnttab[i].len;
total_size += gnttab[i].len;
if (M_TRAILINGSPACE(mbuf) <= 0) {
mbuf = mbuf->m_next;
}
}
mbufc->m_pkthdr.len = total_size;
#if defined(INET) || defined(INET6)
xnb_add_mbuf_cksum(mbufc);
#endif
}
static int
xnb_recv(netif_tx_back_ring_t *txb, domid_t otherend, struct mbuf **mbufc,
if_t ifnet, gnttab_copy_table gnttab)
{
struct xnb_pkt pkt;
int num_consumed;
int nr_ents;
*mbufc = NULL;
num_consumed = xnb_ring2pkt(&pkt, txb, txb->req_cons);
if (num_consumed == 0)
return 0;
if_inc_counter(ifnet, IFCOUNTER_IPACKETS, 1);
if (xnb_pkt_is_valid(&pkt) == 0) {
xnb_txpkt2rsp(&pkt, txb, 1);
txb->req_cons += num_consumed;
DPRINTF("xnb_intr: garbage packet, num_consumed=%d\n",
num_consumed);
if_inc_counter(ifnet, IFCOUNTER_IERRORS, 1);
return EINVAL;
}
*mbufc = xnb_pkt2mbufc(&pkt, ifnet);
if (*mbufc == NULL) {
xnb_txpkt2rsp(&pkt, txb, 1);
DPRINTF("xnb_intr: Couldn't allocate mbufs, num_consumed=%d\n",
num_consumed);
if_inc_counter(ifnet, IFCOUNTER_IQDROPS, 1);
return ENOMEM;
}
nr_ents = xnb_txpkt2gnttab(&pkt, *mbufc, gnttab, txb, otherend);
if (nr_ents > 0) {
int __unused hv_ret = HYPERVISOR_grant_table_op(GNTTABOP_copy,
gnttab, nr_ents);
KASSERT(hv_ret == 0,
("HYPERVISOR_grant_table_op returned %d\n", hv_ret));
xnb_update_mbufc(*mbufc, gnttab, nr_ents);
}
xnb_txpkt2rsp(&pkt, txb, 0);
txb->req_cons += num_consumed;
return 0;
}
static int
xnb_mbufc2pkt(const struct mbuf *mbufc, struct xnb_pkt *pkt,
RING_IDX start, int space)
{
int retval = 0;
if ((mbufc == NULL) ||
( (mbufc->m_flags & M_PKTHDR) == 0) ||
(mbufc->m_pkthdr.len == 0)) {
xnb_pkt_invalidate(pkt);
retval = EINVAL;
} else {
int slots_required;
xnb_pkt_validate(pkt);
pkt->flags = 0;
pkt->size = mbufc->m_pkthdr.len;
pkt->car = start;
pkt->car_size = mbufc->m_len;
if (mbufc->m_pkthdr.csum_flags & CSUM_TSO) {
pkt->flags |= NETRXF_extra_info;
pkt->extra.u.gso.size = mbufc->m_pkthdr.tso_segsz;
pkt->extra.u.gso.type = XEN_NETIF_GSO_TYPE_TCPV4;
pkt->extra.u.gso.pad = 0;
pkt->extra.u.gso.features = 0;
pkt->extra.type = XEN_NETIF_EXTRA_TYPE_GSO;
pkt->extra.flags = 0;
pkt->cdr = start + 2;
} else {
pkt->cdr = start + 1;
}
if (mbufc->m_pkthdr.csum_flags & (CSUM_TSO | CSUM_DELAY_DATA)) {
pkt->flags |=
(NETRXF_csum_blank | NETRXF_data_validated);
}
pkt->list_len = howmany(pkt->size, PAGE_SIZE);
if (pkt->list_len > 1) {
pkt->flags |= NETRXF_more_data;
}
slots_required = pkt->list_len +
(pkt->flags & NETRXF_extra_info ? 1 : 0);
if (slots_required > space) {
xnb_pkt_invalidate(pkt);
retval = EAGAIN;
}
}
return retval;
}
static int
xnb_rxpkt2gnttab(const struct xnb_pkt *pkt, const struct mbuf *mbufc,
gnttab_copy_table gnttab, const netif_rx_back_ring_t *rxb,
domid_t otherend_id)
{
const struct mbuf *mbuf = mbufc;
int gnt_idx = 0;
RING_IDX r_idx = pkt->car;
int r_ofs = 0;
int m_ofs = 0;
uint16_t size_remaining;
size_remaining = (xnb_pkt_is_valid(pkt) != 0) ? pkt->size : 0;
while (size_remaining > 0) {
const netif_rx_request_t *rxq = RING_GET_REQUEST(rxb, r_idx);
const size_t mbuf_space = mbuf->m_len - m_ofs;
const size_t req_size = PAGE_SIZE;
const size_t pkt_space = req_size - r_ofs;
const size_t space = MIN(pkt_space, mbuf_space);
KASSERT(gnt_idx < GNTTAB_LEN, ("Grant table is too short"));
gnttab[gnt_idx].dest.u.ref = rxq->gref;
gnttab[gnt_idx].dest.domid = otherend_id;
gnttab[gnt_idx].dest.offset = r_ofs;
gnttab[gnt_idx].source.u.gmfn = virt_to_mfn(
mtod(mbuf, vm_offset_t) + m_ofs);
gnttab[gnt_idx].source.offset = virt_to_offset(
mtod(mbuf, vm_offset_t) + m_ofs);
gnttab[gnt_idx].source.domid = DOMID_SELF;
gnttab[gnt_idx].len = space;
gnttab[gnt_idx].flags = GNTCOPY_dest_gref;
gnt_idx++;
r_ofs += space;
m_ofs += space;
size_remaining -= space;
if (req_size - r_ofs <= 0) {
r_ofs = 0;
r_idx = (r_idx == pkt->car) ? pkt->cdr : r_idx + 1;
}
if (mbuf->m_len - m_ofs <= 0) {
m_ofs = 0;
mbuf = mbuf->m_next;
}
}
return gnt_idx;
}
static int
xnb_rxpkt2rsp(const struct xnb_pkt *pkt, const gnttab_copy_table gnttab,
int n_entries, netif_rx_back_ring_t *ring)
{
int error = 0;
int gnt_idx, i;
int n_responses = 0;
grant_ref_t last_gref = GRANT_REF_INVALID;
RING_IDX r_idx;
KASSERT(gnttab != NULL, ("Received a null granttable copy"));
for (gnt_idx = 0; gnt_idx < n_entries; gnt_idx++) {
int16_t status = gnttab[gnt_idx].status;
if (status != GNTST_okay) {
DPRINTF(
"Got error %d for hypervisor gnttab_copy status\n",
status);
error = 1;
break;
}
if (gnttab[gnt_idx].dest.u.ref != last_gref) {
n_responses++;
last_gref = gnttab[gnt_idx].dest.u.ref;
}
}
if (error != 0) {
uint16_t id;
netif_rx_response_t *rsp;
id = RING_GET_REQUEST(ring, ring->rsp_prod_pvt)->id;
rsp = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt);
rsp->id = id;
rsp->status = NETIF_RSP_ERROR;
n_responses = 1;
} else {
gnt_idx = 0;
const int has_extra = pkt->flags & NETRXF_extra_info;
if (has_extra != 0)
n_responses++;
for (i = 0; i < n_responses; i++) {
netif_rx_request_t rxq;
netif_rx_response_t *rsp;
r_idx = ring->rsp_prod_pvt + i;
rxq = *(RING_GET_REQUEST(ring, r_idx));
rsp = RING_GET_RESPONSE(ring, r_idx);
if (has_extra && (i == 1)) {
netif_extra_info_t *ext =
(netif_extra_info_t*)rsp;
ext->type = XEN_NETIF_EXTRA_TYPE_GSO;
ext->flags = 0;
ext->u.gso.size = pkt->extra.u.gso.size;
ext->u.gso.type = XEN_NETIF_GSO_TYPE_TCPV4;
ext->u.gso.pad = 0;
ext->u.gso.features = 0;
} else {
rsp->id = rxq.id;
rsp->status = GNTST_okay;
rsp->offset = 0;
rsp->flags = 0;
if (i < pkt->list_len - 1)
rsp->flags |= NETRXF_more_data;
if ((i == 0) && has_extra)
rsp->flags |= NETRXF_extra_info;
if ((i == 0) &&
(pkt->flags & NETRXF_data_validated)) {
rsp->flags |= NETRXF_data_validated;
rsp->flags |= NETRXF_csum_blank;
}
rsp->status = 0;
for (; gnttab[gnt_idx].dest.u.ref == rxq.gref;
gnt_idx++) {
rsp->status += gnttab[gnt_idx].len;
}
}
}
}
ring->req_cons += n_responses;
ring->rsp_prod_pvt += n_responses;
return n_responses;
}
#if defined(INET) || defined(INET6)
static void
xnb_add_mbuf_cksum(struct mbuf *mbufc)
{
struct ether_header *eh;
struct ip *iph;
uint16_t ether_type;
eh = mtod(mbufc, struct ether_header*);
ether_type = ntohs(eh->ether_type);
if (ether_type != ETHERTYPE_IP) {
return;
}
iph = (struct ip*)(eh + 1);
if (mbufc->m_pkthdr.csum_flags & CSUM_IP_VALID) {
iph->ip_sum = 0;
iph->ip_sum = in_cksum_hdr(iph);
}
switch (iph->ip_p) {
case IPPROTO_TCP:
if (mbufc->m_pkthdr.csum_flags & CSUM_IP_VALID) {
size_t tcplen = ntohs(iph->ip_len) - sizeof(struct ip);
struct tcphdr *th = (struct tcphdr*)(iph + 1);
th->th_sum = in_pseudo(iph->ip_src.s_addr,
iph->ip_dst.s_addr, htons(IPPROTO_TCP + tcplen));
th->th_sum = in_cksum_skip(mbufc,
sizeof(struct ether_header) + ntohs(iph->ip_len),
sizeof(struct ether_header) + (iph->ip_hl << 2));
}
break;
case IPPROTO_UDP:
if (mbufc->m_pkthdr.csum_flags & CSUM_IP_VALID) {
size_t udplen = ntohs(iph->ip_len) - sizeof(struct ip);
struct udphdr *uh = (struct udphdr*)(iph + 1);
uh->uh_sum = in_pseudo(iph->ip_src.s_addr,
iph->ip_dst.s_addr, htons(IPPROTO_UDP + udplen));
uh->uh_sum = in_cksum_skip(mbufc,
sizeof(struct ether_header) + ntohs(iph->ip_len),
sizeof(struct ether_header) + (iph->ip_hl << 2));
}
break;
default:
break;
}
}
#endif
static void
xnb_stop(struct xnb_softc *xnb)
{
if_t ifp;
mtx_assert(&xnb->sc_lock, MA_OWNED);
ifp = xnb->xnb_ifp;
if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
if_link_state_change(ifp, LINK_STATE_DOWN);
}
static int
xnb_ioctl(if_t ifp, u_long cmd, caddr_t data)
{
struct xnb_softc *xnb = if_getsoftc(ifp);
struct ifreq *ifr = (struct ifreq*) data;
#ifdef INET
struct ifaddr *ifa = (struct ifaddr*)data;
#endif
int error = 0;
switch (cmd) {
case SIOCSIFFLAGS:
mtx_lock(&xnb->sc_lock);
if (if_getflags(ifp) & IFF_UP) {
xnb_ifinit_locked(xnb);
} else {
if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
xnb_stop(xnb);
}
}
mtx_unlock(&xnb->sc_lock);
break;
case SIOCSIFADDR:
#ifdef INET
mtx_lock(&xnb->sc_lock);
if (ifa->ifa_addr->sa_family == AF_INET) {
if_setflagbits(ifp, IFF_UP, 0);
if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) {
if_setdrvflagbits(ifp, 0,
IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
if_link_state_change(ifp,
LINK_STATE_DOWN);
if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
if_link_state_change(ifp,
LINK_STATE_UP);
}
arp_ifinit(ifp, ifa);
mtx_unlock(&xnb->sc_lock);
} else {
mtx_unlock(&xnb->sc_lock);
#endif
error = ether_ioctl(ifp, cmd, data);
#ifdef INET
}
#endif
break;
case SIOCSIFCAP:
mtx_lock(&xnb->sc_lock);
if (ifr->ifr_reqcap & IFCAP_TXCSUM) {
if_setcapenablebit(ifp, IFCAP_TXCSUM, 0);
if_sethwassistbits(ifp, XNB_CSUM_FEATURES, 0);
} else {
if_setcapenablebit(ifp, 0, IFCAP_TXCSUM);
if_sethwassistbits(ifp, 0, XNB_CSUM_FEATURES);
}
if ((ifr->ifr_reqcap & IFCAP_RXCSUM)) {
if_setcapenablebit(ifp, IFCAP_RXCSUM, 0);
} else {
if_setcapenablebit(ifp, 0, IFCAP_RXCSUM);
}
#if 0
if (ifr->if_reqcap |= IFCAP_TSO4) {
if (IFCAP_TXCSUM & if_getcapenable(ifp)) {
printf("xnb: Xen netif requires that "
"TXCSUM be enabled in order "
"to use TSO4\n");
error = EINVAL;
} else {
if_setcapenablebit(ifp, IFCAP_TSO4, 0);
if_sethwassistbits(ifp, CSUM_TSO, 0);
}
} else {
if_setcapenablebit(ifp, 0, IFCAP_TSO4);
if_sethwassistbits(ifp, 0, (CSUM_TSO));
}
if (ifr->ifreqcap |= IFCAP_LRO) {
if_setcapenablebit(ifp, IFCAP_LRO, 0);
} else {
if_setcapenablebit(ifp, 0, IFCAP_LRO);
}
#endif
mtx_unlock(&xnb->sc_lock);
break;
case SIOCSIFMTU:
if_setmtu(ifp, ifr->ifr_mtu);
if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
xnb_ifinit(xnb);
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
break;
case SIOCSIFMEDIA:
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &xnb->sc_media, cmd);
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
return (error);
}
static void
xnb_start_locked(if_t ifp)
{
netif_rx_back_ring_t *rxb;
struct xnb_softc *xnb;
struct mbuf *mbufc;
RING_IDX req_prod_local;
xnb = if_getsoftc(ifp);
rxb = &xnb->ring_configs[XNB_RING_TYPE_RX].back_ring.rx_ring;
if (!xnb->carrier)
return;
do {
int out_of_space = 0;
int notify;
req_prod_local = rxb->sring->req_prod;
xen_rmb();
for (;;) {
int error;
mbufc = if_dequeue(ifp);
if (mbufc == NULL)
break;
error = xnb_send(rxb, xnb->otherend_id, mbufc,
xnb->rx_gnttab);
switch (error) {
case EAGAIN:
if_sendq_prepend(ifp, mbufc);
out_of_space = 1;
break;
case EINVAL:
if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
default:
if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
if (mbufc)
m_freem(mbufc);
break;
}
if (out_of_space != 0)
break;
}
RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(rxb, notify);
if ((notify != 0) || (out_of_space != 0))
xen_intr_signal(xnb->xen_intr_handle);
rxb->sring->req_event = req_prod_local + 1;
xen_mb();
} while (rxb->sring->req_prod != req_prod_local) ;
}
static int
xnb_send(netif_rx_back_ring_t *ring, domid_t otherend, const struct mbuf *mbufc,
gnttab_copy_table gnttab)
{
struct xnb_pkt pkt;
int error, n_entries;
RING_IDX space;
space = ring->sring->req_prod - ring->req_cons;
error = xnb_mbufc2pkt(mbufc, &pkt, ring->rsp_prod_pvt, space);
if (error != 0)
return error;
n_entries = xnb_rxpkt2gnttab(&pkt, mbufc, gnttab, ring, otherend);
if (n_entries != 0) {
int __unused hv_ret = HYPERVISOR_grant_table_op(GNTTABOP_copy,
gnttab, n_entries);
KASSERT(hv_ret == 0, ("HYPERVISOR_grant_table_op returned %d\n",
hv_ret));
}
xnb_rxpkt2rsp(&pkt, gnttab, n_entries, ring);
return 0;
}
static void
xnb_start(if_t ifp)
{
struct xnb_softc *xnb;
xnb = if_getsoftc(ifp);
mtx_lock(&xnb->rx_lock);
xnb_start_locked(ifp);
mtx_unlock(&xnb->rx_lock);
}
static void
xnb_ifinit_locked(struct xnb_softc *xnb)
{
if_t ifp;
ifp = xnb->xnb_ifp;
mtx_assert(&xnb->sc_lock, MA_OWNED);
if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
return;
xnb_stop(xnb);
if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
if_link_state_change(ifp, LINK_STATE_UP);
}
static void
xnb_ifinit(void *xsc)
{
struct xnb_softc *xnb = xsc;
mtx_lock(&xnb->sc_lock);
xnb_ifinit_locked(xnb);
mtx_unlock(&xnb->sc_lock);
}
static int
xnb_ifmedia_upd(if_t ifp)
{
return (0);
}
static void
xnb_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
{
ifmr->ifm_status = IFM_AVALID|IFM_ACTIVE;
ifmr->ifm_active = IFM_ETHER|IFM_MANUAL;
}
static device_method_t xnb_methods[] = {
DEVMETHOD(device_probe, xnb_probe),
DEVMETHOD(device_attach, xnb_attach),
DEVMETHOD(device_detach, xnb_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD(device_suspend, xnb_suspend),
DEVMETHOD(device_resume, xnb_resume),
DEVMETHOD(xenbus_otherend_changed, xnb_frontend_changed),
DEVMETHOD_END
};
static driver_t xnb_driver = {
"xnb",
xnb_methods,
sizeof(struct xnb_softc),
};
DRIVER_MODULE(xnb, xenbusb_back, xnb_driver, 0, 0);
#ifdef XNB_DEBUG
#include "netback_unit_tests.c"
#endif