#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/devops.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/dlpi.h>
#include <sys/ethernet.h>
#include <sys/strsun.h>
#include <sys/pattr.h>
#include <inet/ip.h>
#include <inet/ip_impl.h>
#include <inet/tcp.h>
#include <netinet/udp.h>
#include <sys/gld.h>
#include <sys/modctl.h>
#include <sys/mac_provider.h>
#include <sys/mac_ether.h>
#include <sys/bootinfo.h>
#include <sys/mach_mmu.h>
#ifdef XPV_HVM_DRIVER
#include <sys/xpv_support.h>
#include <sys/hypervisor.h>
#else
#include <sys/hypervisor.h>
#include <sys/evtchn_impl.h>
#include <sys/balloon_impl.h>
#endif
#include <xen/public/io/netif.h>
#include <sys/gnttab.h>
#include <xen/sys/xendev.h>
#include <sys/sdt.h>
#include <sys/note.h>
#include <sys/debug.h>
#include <io/xnf.h>
#define xnf_btop(addr) ((addr) >> PAGESHIFT)
boolean_t xnf_multicast_control = B_TRUE;
boolean_t xnf_enable_tx_sg = B_TRUE;
boolean_t xnf_enable_rx_sg = B_TRUE;
boolean_t xnf_enable_lso = B_TRUE;
boolean_t xnf_enable_lro = B_FALSE;
size_t xnf_rx_copy_limit = 64;
#define INVALID_GRANT_HANDLE ((grant_handle_t)-1)
#define INVALID_GRANT_REF ((grant_ref_t)-1)
#define INVALID_TX_ID ((uint16_t)-1)
#define TX_ID_TO_TXID(p, id) (&((p)->xnf_tx_pkt_id[(id)]))
#define TX_ID_VALID(i) \
(((i) != INVALID_TX_ID) && ((i) < NET_TX_RING_SIZE))
#define xnf_mblk_pages(mp) (MBLKL(mp) == 0 ? 0 : \
xnf_btop((uintptr_t)mp->b_wptr - 1) - xnf_btop((uintptr_t)mp->b_rptr) + 1)
static int xnf_attach(dev_info_t *, ddi_attach_cmd_t);
static int xnf_detach(dev_info_t *, ddi_detach_cmd_t);
static int xnf_start(void *);
static void xnf_stop(void *);
static int xnf_set_mac_addr(void *, const uint8_t *);
static int xnf_set_multicast(void *, boolean_t, const uint8_t *);
static int xnf_set_promiscuous(void *, boolean_t);
static mblk_t *xnf_send(void *, mblk_t *);
static uint_t xnf_intr(caddr_t);
static int xnf_stat(void *, uint_t, uint64_t *);
static boolean_t xnf_getcapab(void *, mac_capab_t, void *);
static int xnf_getprop(void *, const char *, mac_prop_id_t, uint_t, void *);
static int xnf_setprop(void *, const char *, mac_prop_id_t, uint_t,
const void *);
static void xnf_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
static int xnf_alloc_dma_resources(xnf_t *);
static void xnf_release_dma_resources(xnf_t *);
static void xnf_release_mblks(xnf_t *);
static int xnf_buf_constructor(void *, void *, int);
static void xnf_buf_destructor(void *, void *);
static xnf_buf_t *xnf_buf_get(xnf_t *, int, boolean_t);
static void xnf_buf_put(xnf_t *, xnf_buf_t *, boolean_t);
static void xnf_buf_refresh(xnf_buf_t *);
static void xnf_buf_recycle(xnf_buf_t *);
static int xnf_tx_buf_constructor(void *, void *, int);
static void xnf_tx_buf_destructor(void *, void *);
static grant_ref_t xnf_gref_get(xnf_t *);
static void xnf_gref_put(xnf_t *, grant_ref_t);
static xnf_txid_t *xnf_txid_get(xnf_t *);
static void xnf_txid_put(xnf_t *, xnf_txid_t *);
static void xnf_rxbuf_hang(xnf_t *, xnf_buf_t *);
static int xnf_tx_clean_ring(xnf_t *);
static void oe_state_change(dev_info_t *, ddi_eventcookie_t,
void *, void *);
static boolean_t xnf_kstat_init(xnf_t *);
static void xnf_rx_collect(xnf_t *);
#define XNF_CALLBACK_FLAGS (MC_GETCAPAB | MC_PROPERTIES)
static mac_callbacks_t xnf_callbacks = {
.mc_callbacks = XNF_CALLBACK_FLAGS,
.mc_getstat = xnf_stat,
.mc_start = xnf_start,
.mc_stop = xnf_stop,
.mc_setpromisc = xnf_set_promiscuous,
.mc_multicst = xnf_set_multicast,
.mc_unicst = xnf_set_mac_addr,
.mc_tx = xnf_send,
.mc_getcapab = xnf_getcapab,
.mc_setprop = xnf_setprop,
.mc_getprop = xnf_getprop,
.mc_propinfo = xnf_propinfo,
};
static ddi_dma_attr_t ringbuf_dma_attr = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0,
.dma_attr_addr_hi = 0xffffffffffffffffULL,
.dma_attr_count_max = 0x7fffffff,
.dma_attr_align = MMU_PAGESIZE,
.dma_attr_burstsizes = 0x7ff,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0xffffffffU,
.dma_attr_seg = 0xffffffffffffffffULL,
.dma_attr_sgllen = 1,
.dma_attr_granular = 1,
.dma_attr_flags = 0
};
static ddi_dma_attr_t rx_buf_dma_attr = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0,
.dma_attr_addr_hi = 0xffffffffffffffffULL,
.dma_attr_count_max = MMU_PAGEOFFSET,
.dma_attr_align = MMU_PAGESIZE,
.dma_attr_burstsizes = 0x7ff,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0xffffffffU,
.dma_attr_seg = 0xffffffffffffffffULL,
.dma_attr_sgllen = 1,
.dma_attr_granular = 1,
.dma_attr_flags = 0
};
static ddi_dma_attr_t tx_buf_dma_attr = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0,
.dma_attr_addr_hi = 0xffffffffffffffffULL,
.dma_attr_count_max = MMU_PAGEOFFSET,
.dma_attr_align = 1,
.dma_attr_burstsizes = 0x7ff,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0xffffffffU,
.dma_attr_seg = XEN_DATA_BOUNDARY - 1,
.dma_attr_sgllen = XEN_MAX_TX_DATA_PAGES,
.dma_attr_granular = 1,
.dma_attr_flags = 0
};
static ddi_device_acc_attr_t accattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static ddi_device_acc_attr_t data_accattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
DDI_DEFINE_STREAM_OPS(xnf_dev_ops, nulldev, nulldev, xnf_attach, xnf_detach,
nodev, NULL, D_MP, NULL, ddi_quiesce_not_supported);
static struct modldrv xnf_modldrv = {
&mod_driverops,
"Virtual Ethernet driver",
&xnf_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1, &xnf_modldrv, NULL
};
int
_init(void)
{
int r;
mac_init_ops(&xnf_dev_ops, "xnf");
r = mod_install(&modlinkage);
if (r != DDI_SUCCESS)
mac_fini_ops(&xnf_dev_ops);
return (r);
}
int
_fini(void)
{
return (EBUSY);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static grant_ref_t
xnf_gref_get(xnf_t *xnfp)
{
grant_ref_t gref;
mutex_enter(&xnfp->xnf_gref_lock);
do {
gref = gnttab_claim_grant_reference(&xnfp->xnf_gref_head);
} while ((gref == INVALID_GRANT_REF) &&
(gnttab_alloc_grant_references(16, &xnfp->xnf_gref_head) == 0));
mutex_exit(&xnfp->xnf_gref_lock);
if (gref == INVALID_GRANT_REF) {
xnfp->xnf_stat_gref_failure++;
} else {
atomic_inc_64(&xnfp->xnf_stat_gref_outstanding);
if (xnfp->xnf_stat_gref_outstanding > xnfp->xnf_stat_gref_peak)
xnfp->xnf_stat_gref_peak =
xnfp->xnf_stat_gref_outstanding;
}
return (gref);
}
static void
xnf_gref_put(xnf_t *xnfp, grant_ref_t gref)
{
ASSERT(gref != INVALID_GRANT_REF);
mutex_enter(&xnfp->xnf_gref_lock);
gnttab_release_grant_reference(&xnfp->xnf_gref_head, gref);
mutex_exit(&xnfp->xnf_gref_lock);
atomic_dec_64(&xnfp->xnf_stat_gref_outstanding);
}
static xnf_txid_t *
xnf_txid_get(xnf_t *xnfp)
{
xnf_txid_t *tidp;
ASSERT(MUTEX_HELD(&xnfp->xnf_txlock));
if (xnfp->xnf_tx_pkt_id_head == INVALID_TX_ID)
return (NULL);
ASSERT(TX_ID_VALID(xnfp->xnf_tx_pkt_id_head));
tidp = TX_ID_TO_TXID(xnfp, xnfp->xnf_tx_pkt_id_head);
xnfp->xnf_tx_pkt_id_head = tidp->next;
tidp->next = INVALID_TX_ID;
ASSERT(tidp->txbuf == NULL);
return (tidp);
}
static void
xnf_txid_put(xnf_t *xnfp, xnf_txid_t *tidp)
{
ASSERT(MUTEX_HELD(&xnfp->xnf_txlock));
ASSERT(TX_ID_VALID(tidp->id));
ASSERT(tidp->next == INVALID_TX_ID);
tidp->txbuf = NULL;
tidp->next = xnfp->xnf_tx_pkt_id_head;
xnfp->xnf_tx_pkt_id_head = tidp->id;
}
static void
xnf_data_txbuf_free(xnf_t *xnfp, xnf_txbuf_t *txp)
{
ASSERT3U(txp->tx_type, ==, TX_DATA);
if (txp->tx_bdesc != NULL) {
ASSERT(!txp->tx_handle_bound);
xnf_buf_put(xnfp, txp->tx_bdesc, B_TRUE);
} else {
if (txp->tx_txreq.gref != INVALID_GRANT_REF) {
if (gnttab_query_foreign_access(txp->tx_txreq.gref) !=
0) {
cmn_err(CE_PANIC, "tx grant %d still in use by "
"backend domain", txp->tx_txreq.gref);
}
(void) gnttab_end_foreign_access_ref(
txp->tx_txreq.gref, 1);
xnf_gref_put(xnfp, txp->tx_txreq.gref);
}
if (txp->tx_handle_bound)
(void) ddi_dma_unbind_handle(txp->tx_dma_handle);
}
if (txp->tx_mp != NULL)
freemsg(txp->tx_mp);
if (txp->tx_prev != NULL) {
ASSERT3P(txp->tx_prev->tx_next, ==, txp);
txp->tx_prev->tx_next = NULL;
}
if (txp->tx_txreq.id != INVALID_TX_ID) {
ASSERT(!xnfp->xnf_connected);
xnf_txid_put(xnfp, TX_ID_TO_TXID(xnfp, txp->tx_txreq.id));
txp->tx_txreq.id = INVALID_TX_ID;
}
kmem_cache_free(xnfp->xnf_tx_buf_cache, txp);
}
static void
xnf_data_txbuf_free_chain(xnf_t *xnfp, xnf_txbuf_t *txp)
{
if (txp == NULL)
return;
while (txp->tx_next != NULL)
txp = txp->tx_next;
xnf_txbuf_t *prev;
for (; txp != NULL; txp = prev) {
prev = txp->tx_prev;
xnf_data_txbuf_free(xnfp, txp);
}
}
static xnf_txbuf_t *
xnf_data_txbuf_alloc(xnf_t *xnfp, int flag)
{
xnf_txbuf_t *txp;
if ((txp = kmem_cache_alloc(xnfp->xnf_tx_buf_cache, flag)) == NULL) {
return (NULL);
}
txp->tx_type = TX_DATA;
txp->tx_next = NULL;
txp->tx_prev = NULL;
txp->tx_head = txp;
txp->tx_frags_to_ack = 0;
txp->tx_mp = NULL;
txp->tx_bdesc = NULL;
txp->tx_handle_bound = B_FALSE;
txp->tx_txreq.gref = INVALID_GRANT_REF;
txp->tx_txreq.id = INVALID_TX_ID;
return (txp);
}
static int
xnf_tx_slots_get(xnf_t *xnfp, int wanted, boolean_t wait)
{
int slotsfree;
boolean_t forced_clean = (wanted == 0);
ASSERT(MUTEX_HELD(&xnfp->xnf_txlock));
while (B_TRUE) {
slotsfree = RING_FREE_REQUESTS(&xnfp->xnf_tx_ring);
if ((slotsfree < wanted) || forced_clean)
slotsfree = xnf_tx_clean_ring(xnfp);
if (slotsfree > wanted)
cv_broadcast(&xnfp->xnf_cv_tx_slots);
if (slotsfree >= wanted)
break;
if (!wait)
break;
cv_wait(&xnfp->xnf_cv_tx_slots, &xnfp->xnf_txlock);
}
ASSERT(slotsfree <= RING_SIZE(&(xnfp->xnf_tx_ring)));
return (slotsfree);
}
static int
xnf_setup_rings(xnf_t *xnfp)
{
domid_t oeid;
struct xenbus_device *xsd;
RING_IDX i;
int err;
xnf_txid_t *tidp;
xnf_buf_t **bdescp;
oeid = xvdi_get_oeid(xnfp->xnf_devinfo);
xsd = xvdi_get_xsd(xnfp->xnf_devinfo);
if (xnfp->xnf_tx_ring_ref != INVALID_GRANT_REF)
gnttab_end_foreign_access(xnfp->xnf_tx_ring_ref, 0, 0);
err = gnttab_grant_foreign_access(oeid,
xnf_btop(pa_to_ma(xnfp->xnf_tx_ring_phys_addr)), 0);
if (err <= 0) {
err = -err;
xenbus_dev_error(xsd, err, "granting access to tx ring page");
goto out;
}
xnfp->xnf_tx_ring_ref = (grant_ref_t)err;
if (xnfp->xnf_rx_ring_ref != INVALID_GRANT_REF)
gnttab_end_foreign_access(xnfp->xnf_rx_ring_ref, 0, 0);
err = gnttab_grant_foreign_access(oeid,
xnf_btop(pa_to_ma(xnfp->xnf_rx_ring_phys_addr)), 0);
if (err <= 0) {
err = -err;
xenbus_dev_error(xsd, err, "granting access to rx ring page");
goto out;
}
xnfp->xnf_rx_ring_ref = (grant_ref_t)err;
mutex_enter(&xnfp->xnf_txlock);
xnfp->xnf_tx_pkt_id_head = INVALID_TX_ID;
for (i = 0, tidp = &xnfp->xnf_tx_pkt_id[0];
i < NET_TX_RING_SIZE;
i++, tidp++) {
xnf_txbuf_t *txp = tidp->txbuf;
if (txp == NULL)
continue;
switch (txp->tx_type) {
case TX_DATA:
xnf_data_txbuf_free_chain(xnfp, txp);
break;
case TX_MCAST_REQ:
txp->tx_type = TX_MCAST_RSP;
txp->tx_status = NETIF_RSP_DROPPED;
cv_broadcast(&xnfp->xnf_cv_multicast);
i++;
ASSERT3U(i, <, NET_TX_RING_SIZE);
break;
case TX_MCAST_RSP:
break;
}
}
xnfp->xnf_tx_pkt_id_head = INVALID_TX_ID;
for (i = 0, tidp = &xnfp->xnf_tx_pkt_id[0];
i < NET_TX_RING_SIZE;
i++, tidp++) {
tidp->id = i;
ASSERT3P(tidp->txbuf, ==, NULL);
tidp->next = INVALID_TX_ID;
xnf_txid_put(xnfp, tidp);
}
SHARED_RING_INIT(xnfp->xnf_tx_ring.sring);
FRONT_RING_INIT(&xnfp->xnf_tx_ring,
xnfp->xnf_tx_ring.sring, PAGESIZE);
mutex_exit(&xnfp->xnf_txlock);
mutex_enter(&xnfp->xnf_rxlock);
for (i = 0, bdescp = &xnfp->xnf_rx_pkt_info[0];
i < NET_RX_RING_SIZE;
i++, bdescp++) {
if (*bdescp != NULL) {
xnf_buf_put(xnfp, *bdescp, B_FALSE);
*bdescp = NULL;
}
}
SHARED_RING_INIT(xnfp->xnf_rx_ring.sring);
FRONT_RING_INIT(&xnfp->xnf_rx_ring,
xnfp->xnf_rx_ring.sring, PAGESIZE);
for (i = 0; i < NET_RX_RING_SIZE; i++) {
xnf_buf_t *bdesc;
bdesc = xnf_buf_get(xnfp, KM_SLEEP, B_FALSE);
VERIFY(bdesc != NULL);
xnf_rxbuf_hang(xnfp, bdesc);
}
RING_PUSH_REQUESTS(&xnfp->xnf_rx_ring);
mutex_exit(&xnfp->xnf_rxlock);
return (0);
out:
if (xnfp->xnf_tx_ring_ref != INVALID_GRANT_REF)
gnttab_end_foreign_access(xnfp->xnf_tx_ring_ref, 0, 0);
xnfp->xnf_tx_ring_ref = INVALID_GRANT_REF;
if (xnfp->xnf_rx_ring_ref != INVALID_GRANT_REF)
gnttab_end_foreign_access(xnfp->xnf_rx_ring_ref, 0, 0);
xnfp->xnf_rx_ring_ref = INVALID_GRANT_REF;
return (err);
}
void
xnf_be_connect(xnf_t *xnfp)
{
const char *message;
xenbus_transaction_t xbt;
struct xenbus_device *xsd;
char *xsname;
int err;
ASSERT(!xnfp->xnf_connected);
xsd = xvdi_get_xsd(xnfp->xnf_devinfo);
xsname = xvdi_get_xsname(xnfp->xnf_devinfo);
err = xnf_setup_rings(xnfp);
if (err != 0) {
cmn_err(CE_WARN, "failed to set up tx/rx rings");
xenbus_dev_error(xsd, err, "setting up ring");
return;
}
again:
err = xenbus_transaction_start(&xbt);
if (err != 0) {
xenbus_dev_error(xsd, EIO, "starting transaction");
return;
}
err = xenbus_printf(xbt, xsname, "tx-ring-ref", "%u",
xnfp->xnf_tx_ring_ref);
if (err != 0) {
message = "writing tx ring-ref";
goto abort_transaction;
}
err = xenbus_printf(xbt, xsname, "rx-ring-ref", "%u",
xnfp->xnf_rx_ring_ref);
if (err != 0) {
message = "writing rx ring-ref";
goto abort_transaction;
}
err = xenbus_printf(xbt, xsname, "event-channel", "%u",
xnfp->xnf_evtchn);
if (err != 0) {
message = "writing event-channel";
goto abort_transaction;
}
err = xenbus_printf(xbt, xsname, "feature-rx-notify", "%d", 1);
if (err != 0) {
message = "writing feature-rx-notify";
goto abort_transaction;
}
err = xenbus_printf(xbt, xsname, "request-rx-copy", "%d", 1);
if (err != 0) {
message = "writing request-rx-copy";
goto abort_transaction;
}
if (xnfp->xnf_be_mcast_control) {
err = xenbus_printf(xbt, xsname, "request-multicast-control",
"%d", 1);
if (err != 0) {
message = "writing request-multicast-control";
goto abort_transaction;
}
}
err = xenbus_printf(xbt, xsname, "feature-sg", "%d",
xnf_enable_rx_sg ? 1 : 0);
if (err != 0) {
message = "writing feature-sg";
goto abort_transaction;
}
err = xenbus_printf(xbt, xsname, "feature-gso-tcpv4", "%d",
(xnf_enable_rx_sg && xnf_enable_lro) ? 1 : 0);
if (err != 0) {
message = "writing feature-gso-tcpv4";
goto abort_transaction;
}
err = xvdi_switch_state(xnfp->xnf_devinfo, xbt, XenbusStateConnected);
if (err != 0) {
message = "switching state to XenbusStateConnected";
goto abort_transaction;
}
err = xenbus_transaction_end(xbt, 0);
if (err != 0) {
if (err == EAGAIN)
goto again;
xenbus_dev_error(xsd, err, "completing transaction");
}
return;
abort_transaction:
(void) xenbus_transaction_end(xbt, 1);
xenbus_dev_error(xsd, err, "%s", message);
}
void
xnf_read_config(xnf_t *xnfp)
{
int err, be_cap;
char mac[ETHERADDRL * 3];
char *oename = xvdi_get_oename(xnfp->xnf_devinfo);
err = xenbus_scanf(XBT_NULL, oename, "mac",
"%s", (char *)&mac[0]);
if (err != 0) {
cmn_err(CE_WARN, "%s%d: no mac address",
ddi_driver_name(xnfp->xnf_devinfo),
ddi_get_instance(xnfp->xnf_devinfo));
return;
}
if (ether_aton(mac, xnfp->xnf_mac_addr) != ETHERADDRL) {
err = ENOENT;
xenbus_dev_error(xvdi_get_xsd(xnfp->xnf_devinfo), ENOENT,
"parsing %s/mac", xvdi_get_xsname(xnfp->xnf_devinfo));
return;
}
err = xenbus_scanf(XBT_NULL, oename,
"feature-rx-copy", "%d", &be_cap);
if (err != 0)
be_cap = 0;
xnfp->xnf_be_rx_copy = (be_cap != 0);
err = xenbus_scanf(XBT_NULL, oename,
"feature-multicast-control", "%d", &be_cap);
if (err != 0)
be_cap = 0;
xnfp->xnf_be_mcast_control = (be_cap != 0) && xnf_multicast_control;
err = xenbus_scanf(XBT_NULL, oename, "feature-sg", "%d", &be_cap);
if (err != 0) {
be_cap = 0;
dev_err(xnfp->xnf_devinfo, CE_WARN, "error reading "
"'feature-sg' from backend driver");
}
if (be_cap == 0) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "scatter-gather is not "
"supported for transmits in the backend driver. LSO is "
"disabled and MTU is restricted to 1500 bytes.");
}
xnfp->xnf_be_tx_sg = (be_cap != 0) && xnf_enable_tx_sg;
if (xnfp->xnf_be_tx_sg) {
err = xenbus_scanf(XBT_NULL, oename, "feature-gso-tcpv4", "%d",
&be_cap);
if (err != 0) {
be_cap = 0;
dev_err(xnfp->xnf_devinfo, CE_WARN, "error reading "
"'feature-gso-tcpv4' from backend driver");
}
if (be_cap == 0) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "LSO is not "
"supported by the backend driver. Performance "
"will be affected.");
}
xnfp->xnf_be_lso = (be_cap != 0) && xnf_enable_lso;
}
}
static int
xnf_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
{
mac_register_t *macp;
xnf_t *xnfp;
int err;
char cachename[32];
switch (cmd) {
case DDI_RESUME:
xnfp = ddi_get_driver_private(devinfo);
xnfp->xnf_gen++;
(void) xvdi_resume(devinfo);
(void) xvdi_alloc_evtchn(devinfo);
xnfp->xnf_evtchn = xvdi_get_evtchn(devinfo);
#ifdef XPV_HVM_DRIVER
ec_bind_evtchn_to_handler(xnfp->xnf_evtchn, IPL_VIF, xnf_intr,
xnfp);
#else
(void) ddi_add_intr(devinfo, 0, NULL, NULL, xnf_intr,
(caddr_t)xnfp);
#endif
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
macp = mac_alloc(MAC_VERSION);
if (macp == NULL)
return (DDI_FAILURE);
xnfp = kmem_zalloc(sizeof (*xnfp), KM_SLEEP);
xnfp->xnf_tx_pkt_id =
kmem_zalloc(sizeof (xnf_txid_t) * NET_TX_RING_SIZE, KM_SLEEP);
xnfp->xnf_rx_pkt_info =
kmem_zalloc(sizeof (xnf_buf_t *) * NET_RX_RING_SIZE, KM_SLEEP);
macp->m_dip = devinfo;
macp->m_driver = xnfp;
xnfp->xnf_devinfo = devinfo;
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_src_addr = xnfp->xnf_mac_addr;
macp->m_callbacks = &xnf_callbacks;
macp->m_min_sdu = 0;
xnfp->xnf_mtu = ETHERMTU;
macp->m_max_sdu = xnfp->xnf_mtu;
xnfp->xnf_running = B_FALSE;
xnfp->xnf_connected = B_FALSE;
xnfp->xnf_be_rx_copy = B_FALSE;
xnfp->xnf_be_mcast_control = B_FALSE;
xnfp->xnf_need_sched = B_FALSE;
xnfp->xnf_rx_head = NULL;
xnfp->xnf_rx_tail = NULL;
xnfp->xnf_rx_new_buffers_posted = B_FALSE;
#ifdef XPV_HVM_DRIVER
(void) xenbus_printf(XBT_NULL, "guest/xnf", "version", "%d",
HVMPV_XNF_VERS);
#endif
if (ddi_get_iblock_cookie(devinfo, 0, &xnfp->xnf_icookie)
!= DDI_SUCCESS)
goto failure;
mutex_init(&xnfp->xnf_txlock,
NULL, MUTEX_DRIVER, xnfp->xnf_icookie);
mutex_init(&xnfp->xnf_rxlock,
NULL, MUTEX_DRIVER, xnfp->xnf_icookie);
mutex_init(&xnfp->xnf_schedlock,
NULL, MUTEX_DRIVER, xnfp->xnf_icookie);
mutex_init(&xnfp->xnf_gref_lock,
NULL, MUTEX_DRIVER, xnfp->xnf_icookie);
cv_init(&xnfp->xnf_cv_state, NULL, CV_DEFAULT, NULL);
cv_init(&xnfp->xnf_cv_multicast, NULL, CV_DEFAULT, NULL);
cv_init(&xnfp->xnf_cv_tx_slots, NULL, CV_DEFAULT, NULL);
(void) sprintf(cachename, "xnf_buf_cache_%d",
ddi_get_instance(devinfo));
xnfp->xnf_buf_cache = kmem_cache_create(cachename,
sizeof (xnf_buf_t), 0,
xnf_buf_constructor, xnf_buf_destructor,
NULL, xnfp, NULL, 0);
if (xnfp->xnf_buf_cache == NULL)
goto failure_0;
(void) sprintf(cachename, "xnf_tx_buf_cache_%d",
ddi_get_instance(devinfo));
xnfp->xnf_tx_buf_cache = kmem_cache_create(cachename,
sizeof (xnf_txbuf_t), 0,
xnf_tx_buf_constructor, xnf_tx_buf_destructor,
NULL, xnfp, NULL, 0);
if (xnfp->xnf_tx_buf_cache == NULL)
goto failure_1;
xnfp->xnf_gref_head = INVALID_GRANT_REF;
if (xnf_alloc_dma_resources(xnfp) == DDI_FAILURE) {
cmn_err(CE_WARN, "xnf%d: failed to allocate and initialize "
"driver data structures",
ddi_get_instance(xnfp->xnf_devinfo));
goto failure_2;
}
xnfp->xnf_rx_ring.sring->rsp_event =
xnfp->xnf_tx_ring.sring->rsp_event = 1;
xnfp->xnf_tx_ring_ref = INVALID_GRANT_REF;
xnfp->xnf_rx_ring_ref = INVALID_GRANT_REF;
ddi_set_driver_private(devinfo, xnfp);
if (!xnf_kstat_init(xnfp))
goto failure_3;
(void) xvdi_alloc_evtchn(devinfo);
xnfp->xnf_evtchn = xvdi_get_evtchn(devinfo);
#ifdef XPV_HVM_DRIVER
ec_bind_evtchn_to_handler(xnfp->xnf_evtchn, IPL_VIF, xnf_intr, xnfp);
#else
(void) ddi_add_intr(devinfo, 0, NULL, NULL, xnf_intr, (caddr_t)xnfp);
#endif
err = mac_register(macp, &xnfp->xnf_mh);
mac_free(macp);
macp = NULL;
if (err != 0)
goto failure_4;
if (xvdi_add_event_handler(devinfo, XS_OE_STATE, oe_state_change, NULL)
!= DDI_SUCCESS)
goto failure_5;
#ifdef XPV_HVM_DRIVER
(void) ndi_prop_update_string(DDI_DEV_T_NONE, devinfo, "model",
"Ethernet controller");
#endif
return (DDI_SUCCESS);
failure_5:
(void) mac_unregister(xnfp->xnf_mh);
failure_4:
#ifdef XPV_HVM_DRIVER
ec_unbind_evtchn(xnfp->xnf_evtchn);
xvdi_free_evtchn(devinfo);
#else
ddi_remove_intr(devinfo, 0, xnfp->xnf_icookie);
#endif
xnfp->xnf_evtchn = INVALID_EVTCHN;
kstat_delete(xnfp->xnf_kstat_aux);
failure_3:
xnf_release_dma_resources(xnfp);
failure_2:
kmem_cache_destroy(xnfp->xnf_tx_buf_cache);
failure_1:
kmem_cache_destroy(xnfp->xnf_buf_cache);
failure_0:
cv_destroy(&xnfp->xnf_cv_tx_slots);
cv_destroy(&xnfp->xnf_cv_multicast);
cv_destroy(&xnfp->xnf_cv_state);
mutex_destroy(&xnfp->xnf_gref_lock);
mutex_destroy(&xnfp->xnf_schedlock);
mutex_destroy(&xnfp->xnf_rxlock);
mutex_destroy(&xnfp->xnf_txlock);
failure:
kmem_free(xnfp, sizeof (*xnfp));
if (macp != NULL)
mac_free(macp);
return (DDI_FAILURE);
}
static int
xnf_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd)
{
xnf_t *xnfp;
xnfp = ddi_get_driver_private(devinfo);
switch (cmd) {
case DDI_SUSPEND:
#ifdef XPV_HVM_DRIVER
ec_unbind_evtchn(xnfp->xnf_evtchn);
xvdi_free_evtchn(devinfo);
#else
ddi_remove_intr(devinfo, 0, xnfp->xnf_icookie);
#endif
xvdi_suspend(devinfo);
mutex_enter(&xnfp->xnf_rxlock);
mutex_enter(&xnfp->xnf_txlock);
xnfp->xnf_evtchn = INVALID_EVTCHN;
xnfp->xnf_connected = B_FALSE;
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
mac_link_update(xnfp->xnf_mh, LINK_STATE_DOWN);
return (DDI_SUCCESS);
case DDI_DETACH:
break;
default:
return (DDI_FAILURE);
}
if (xnfp->xnf_connected)
return (DDI_FAILURE);
if (xnfp->xnf_stat_buf_allocated > 0)
return (DDI_FAILURE);
if (mac_unregister(xnfp->xnf_mh) != 0)
return (DDI_FAILURE);
kstat_delete(xnfp->xnf_kstat_aux);
xnf_stop(xnfp);
xvdi_remove_event_handler(devinfo, XS_OE_STATE);
#ifdef XPV_HVM_DRIVER
ec_unbind_evtchn(xnfp->xnf_evtchn);
xvdi_free_evtchn(devinfo);
#else
ddi_remove_intr(devinfo, 0, xnfp->xnf_icookie);
#endif
xnf_release_mblks(xnfp);
xnf_release_dma_resources(xnfp);
cv_destroy(&xnfp->xnf_cv_tx_slots);
cv_destroy(&xnfp->xnf_cv_multicast);
cv_destroy(&xnfp->xnf_cv_state);
kmem_cache_destroy(xnfp->xnf_tx_buf_cache);
kmem_cache_destroy(xnfp->xnf_buf_cache);
mutex_destroy(&xnfp->xnf_gref_lock);
mutex_destroy(&xnfp->xnf_schedlock);
mutex_destroy(&xnfp->xnf_rxlock);
mutex_destroy(&xnfp->xnf_txlock);
kmem_free(xnfp, sizeof (*xnfp));
return (DDI_SUCCESS);
}
static int
xnf_set_mac_addr(void *arg, const uint8_t *macaddr)
{
_NOTE(ARGUNUSED(arg, macaddr));
return (ENOTSUP);
}
static int
xnf_set_multicast(void *arg, boolean_t add, const uint8_t *mca)
{
xnf_t *xnfp = arg;
xnf_txbuf_t *txp;
int n_slots;
RING_IDX slot;
xnf_txid_t *tidp;
netif_tx_request_t *txrp;
struct netif_extra_info *erp;
boolean_t notify, result;
if (!xnfp->xnf_be_mcast_control)
return (0);
txp = kmem_cache_alloc(xnfp->xnf_tx_buf_cache, KM_SLEEP);
mutex_enter(&xnfp->xnf_txlock);
if (!xnfp->xnf_connected) {
mutex_exit(&xnfp->xnf_txlock);
return (0);
}
n_slots = xnf_tx_slots_get(xnfp, 2, B_TRUE);
ASSERT(n_slots >= 2);
slot = xnfp->xnf_tx_ring.req_prod_pvt;
tidp = xnf_txid_get(xnfp);
VERIFY(tidp != NULL);
txp->tx_type = TX_MCAST_REQ;
txp->tx_slot = slot;
txrp = RING_GET_REQUEST(&xnfp->xnf_tx_ring, slot);
erp = (struct netif_extra_info *)
RING_GET_REQUEST(&xnfp->xnf_tx_ring, slot + 1);
txrp->gref = 0;
txrp->size = 0;
txrp->offset = 0;
txrp->id = txp->tx_txreq.id = tidp->id;
txrp->flags = NETTXF_extra_info;
erp->type = add ? XEN_NETIF_EXTRA_TYPE_MCAST_ADD :
XEN_NETIF_EXTRA_TYPE_MCAST_DEL;
bcopy((void *)mca, &erp->u.mcast.addr, ETHERADDRL);
tidp->txbuf = txp;
xnfp->xnf_tx_ring.req_prod_pvt = slot + 2;
mutex_enter(&xnfp->xnf_schedlock);
xnfp->xnf_pending_multicast++;
mutex_exit(&xnfp->xnf_schedlock);
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&xnfp->xnf_tx_ring,
notify);
if (notify)
ec_notify_via_evtchn(xnfp->xnf_evtchn);
while (txp->tx_type == TX_MCAST_REQ)
cv_wait(&xnfp->xnf_cv_multicast, &xnfp->xnf_txlock);
ASSERT3U(txp->tx_type, ==, TX_MCAST_RSP);
mutex_enter(&xnfp->xnf_schedlock);
xnfp->xnf_pending_multicast--;
mutex_exit(&xnfp->xnf_schedlock);
result = (txp->tx_status == NETIF_RSP_OKAY);
xnf_txid_put(xnfp, tidp);
mutex_exit(&xnfp->xnf_txlock);
kmem_cache_free(xnfp->xnf_tx_buf_cache, txp);
return (result ? 0 : 1);
}
static int
xnf_set_promiscuous(void *arg, boolean_t on)
{
_NOTE(ARGUNUSED(arg, on));
return (0);
}
static int
xnf_tx_clean_ring(xnf_t *xnfp)
{
boolean_t work_to_do;
ASSERT(MUTEX_HELD(&xnfp->xnf_txlock));
loop:
while (RING_HAS_UNCONSUMED_RESPONSES(&xnfp->xnf_tx_ring)) {
RING_IDX cons, prod, i;
cons = xnfp->xnf_tx_ring.rsp_cons;
prod = xnfp->xnf_tx_ring.sring->rsp_prod;
membar_consumer();
DTRACE_PROBE2(xnf_tx_clean_range, int, cons, int, prod);
for (i = cons; i != prod; i++) {
netif_tx_response_t *trp;
xnf_txid_t *tidp;
xnf_txbuf_t *txp;
trp = RING_GET_RESPONSE(&xnfp->xnf_tx_ring, i);
if (trp->status == NETIF_RSP_NULL)
continue;
ASSERT(TX_ID_VALID(trp->id));
tidp = TX_ID_TO_TXID(xnfp, trp->id);
ASSERT3U(tidp->id, ==, trp->id);
ASSERT3U(tidp->next, ==, INVALID_TX_ID);
txp = tidp->txbuf;
ASSERT(txp != NULL);
ASSERT3U(txp->tx_txreq.id, ==, trp->id);
switch (txp->tx_type) {
case TX_DATA:
xnf_txid_put(xnfp, tidp);
txp->tx_txreq.id = INVALID_TX_ID;
ASSERT3S(txp->tx_head->tx_frags_to_ack, >, 0);
txp->tx_head->tx_frags_to_ack--;
if (txp->tx_head->tx_frags_to_ack == 0)
xnf_data_txbuf_free_chain(xnfp, txp);
break;
case TX_MCAST_REQ:
txp->tx_type = TX_MCAST_RSP;
txp->tx_status = trp->status;
cv_broadcast(&xnfp->xnf_cv_multicast);
break;
default:
cmn_err(CE_PANIC, "xnf_tx_clean_ring: "
"invalid xnf_txbuf_t type: %d",
txp->tx_type);
break;
}
}
xnfp->xnf_tx_ring.rsp_cons = prod;
membar_enter();
}
RING_FINAL_CHECK_FOR_RESPONSES(&xnfp->xnf_tx_ring, work_to_do);
if (work_to_do)
goto loop;
return (RING_FREE_REQUESTS(&xnfp->xnf_tx_ring));
}
static xnf_buf_t *
xnf_tx_get_lookaside(xnf_t *xnfp, mblk_t *mp, size_t *plen)
{
xnf_buf_t *bd;
caddr_t bp;
if ((bd = xnf_buf_get(xnfp, KM_NOSLEEP, B_TRUE)) == NULL) {
return (NULL);
}
bp = bd->buf;
while (mp != NULL) {
size_t len = MBLKL(mp);
bcopy(mp->b_rptr, bp, len);
bp += len;
mp = mp->b_cont;
}
*plen = bp - bd->buf;
ASSERT3U(*plen, <=, PAGESIZE);
xnfp->xnf_stat_tx_lookaside++;
return (bd);
}
int
xnf_pseudo_cksum(mblk_t *mp)
{
struct ether_header *ehp;
uint16_t sap, iplen, *stuff;
uint32_t cksum;
size_t len;
ipha_t *ipha;
ipaddr_t src, dst;
uchar_t *ptr;
ptr = mp->b_rptr;
len = MBLKL(mp);
ASSERT3U(len, >=, sizeof (*ehp));
ehp = (struct ether_header *)ptr;
if (ntohs(ehp->ether_type) == VLAN_TPID) {
struct ether_vlan_header *evhp;
ASSERT3U(len, >=, sizeof (*evhp));
evhp = (struct ether_vlan_header *)ptr;
sap = ntohs(evhp->ether_type);
ptr += sizeof (*evhp);
len -= sizeof (*evhp);
} else {
sap = ntohs(ehp->ether_type);
ptr += sizeof (*ehp);
len -= sizeof (*ehp);
}
ASSERT3U(sap, ==, ETHERTYPE_IP);
ASSERT3P(ptr, <=, mp->b_wptr);
if (ptr == mp->b_wptr) {
mp = mp->b_cont;
ptr = mp->b_rptr;
len = MBLKL(mp);
}
ASSERT3U(len, >=, sizeof (ipha_t));
ipha = (ipha_t *)ptr;
ASSERT3U(IPH_HDR_LENGTH(ipha), ==, IP_SIMPLE_HDR_LENGTH);
iplen = ntohs(ipha->ipha_length) - IP_SIMPLE_HDR_LENGTH;
ptr += IP_SIMPLE_HDR_LENGTH;
len -= IP_SIMPLE_HDR_LENGTH;
ASSERT3P(ptr, <=, mp->b_wptr);
if (ptr == mp->b_wptr) {
mp = mp->b_cont;
ptr = mp->b_rptr;
len = MBLKL(mp);
}
switch (ipha->ipha_protocol) {
case IPPROTO_TCP:
ASSERT3U(len, >=, sizeof (tcph_t));
stuff = (uint16_t *)(ptr + TCP_CHECKSUM_OFFSET);
cksum = IP_TCP_CSUM_COMP;
break;
case IPPROTO_UDP:
ASSERT3U(len, >=, sizeof (struct udphdr));
stuff = (uint16_t *)(ptr + UDP_CHECKSUM_OFFSET);
cksum = IP_UDP_CSUM_COMP;
break;
default:
cmn_err(CE_WARN, "xnf_pseudo_cksum: unexpected protocol %d",
ipha->ipha_protocol);
return (EINVAL);
}
src = ipha->ipha_src;
dst = ipha->ipha_dst;
cksum += (dst >> 16) + (dst & 0xFFFF);
cksum += (src >> 16) + (src & 0xFFFF);
cksum += htons(iplen);
cksum = (cksum >> 16) + (cksum & 0xFFFF);
cksum = (cksum >> 16) + (cksum & 0xFFFF);
ASSERT(cksum <= 0xFFFF);
*stuff = (uint16_t)(cksum ? cksum : ~cksum);
return (0);
}
static void
xnf_tx_push_packet(xnf_t *xnfp, xnf_txbuf_t *head)
{
int nslots = 0;
int extras = 0;
RING_IDX slot;
boolean_t notify;
ASSERT(MUTEX_HELD(&xnfp->xnf_txlock));
ASSERT(xnfp->xnf_running);
slot = xnfp->xnf_tx_ring.req_prod_pvt;
for (xnf_txbuf_t *txp = head; txp != NULL; txp = txp->tx_next) {
xnf_txid_t *tidp;
netif_tx_request_t *txrp;
tidp = xnf_txid_get(xnfp);
VERIFY(tidp != NULL);
txrp = RING_GET_REQUEST(&xnfp->xnf_tx_ring, slot);
txp->tx_slot = slot;
txp->tx_txreq.id = tidp->id;
*txrp = txp->tx_txreq;
tidp->txbuf = txp;
slot++;
nslots++;
if (txp->tx_txreq.flags & NETTXF_extra_info) {
netif_extra_info_t *extra;
ASSERT3U(nslots, ==, 1);
extra = (netif_extra_info_t *)
RING_GET_REQUEST(&xnfp->xnf_tx_ring, slot);
*extra = txp->tx_extra;
slot++;
nslots++;
extras = 1;
}
}
ASSERT3U(nslots, <=, XEN_MAX_SLOTS_PER_TX);
head->tx_frags_to_ack = nslots - extras;
xnfp->xnf_tx_ring.req_prod_pvt = slot;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&xnfp->xnf_tx_ring, notify);
if (notify)
ec_notify_via_evtchn(xnfp->xnf_evtchn);
}
static xnf_txbuf_t *
xnf_mblk_copy(xnf_t *xnfp, mblk_t *mp)
{
xnf_txbuf_t *txp;
size_t length;
if ((txp = xnf_data_txbuf_alloc(xnfp, KM_NOSLEEP)) == NULL) {
return (NULL);
}
txp->tx_bdesc = xnf_tx_get_lookaside(xnfp, mp, &length);
if (txp->tx_bdesc == NULL) {
xnf_data_txbuf_free(xnfp, txp);
return (NULL);
}
txp->tx_mfn = txp->tx_bdesc->buf_mfn;
txp->tx_txreq.gref = txp->tx_bdesc->grant_ref;
txp->tx_txreq.size = length;
txp->tx_txreq.offset = (uintptr_t)txp->tx_bdesc->buf & PAGEOFFSET;
txp->tx_txreq.flags = 0;
return (txp);
}
static xnf_txbuf_t *
xnf_mblk_map(xnf_t *xnfp, mblk_t *mp, int *countp)
{
xnf_txbuf_t *head = NULL;
xnf_txbuf_t *tail = NULL;
domid_t oeid;
int nsegs = 0;
oeid = xvdi_get_oeid(xnfp->xnf_devinfo);
for (mblk_t *ml = mp; ml != NULL; ml = ml->b_cont) {
ddi_dma_handle_t dma_handle;
const ddi_dma_cookie_t *dma_cookie, *dma_cookie_prev;
xnf_txbuf_t *txp;
if (MBLKL(ml) == 0)
continue;
if ((txp = xnf_data_txbuf_alloc(xnfp, KM_NOSLEEP)) == NULL) {
goto error;
}
if (head == NULL) {
head = txp;
} else {
ASSERT(tail != NULL);
TXBUF_SETNEXT(tail, txp);
txp->tx_head = head;
}
dma_handle = txp->tx_dma_handle;
int ret = ddi_dma_addr_bind_handle(dma_handle,
NULL, (char *)ml->b_rptr, MBLKL(ml),
DDI_DMA_WRITE | DDI_DMA_STREAMING,
DDI_DMA_DONTWAIT, 0, NULL, NULL);
if (ret != DDI_DMA_MAPPED) {
if (ret != DDI_DMA_NORESOURCES) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"ddi_dma_addr_bind_handle() failed "
"[dma_error=%d]", ret);
}
goto error;
}
txp->tx_handle_bound = B_TRUE;
dma_cookie_prev = NULL;
while ((dma_cookie = ddi_dma_cookie_iter(dma_handle,
dma_cookie_prev)) != NULL) {
if (nsegs == XEN_MAX_TX_DATA_PAGES) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"xnf_dmamap_alloc() failed: "
"too many segments");
goto error;
}
if (dma_cookie_prev != NULL) {
if ((txp = xnf_data_txbuf_alloc(xnfp,
KM_NOSLEEP)) == NULL) {
goto error;
}
ASSERT(tail != NULL);
TXBUF_SETNEXT(tail, txp);
txp->tx_head = head;
}
txp->tx_mfn =
xnf_btop(pa_to_ma(dma_cookie->dmac_laddress));
txp->tx_txreq.gref = xnf_gref_get(xnfp);
if (txp->tx_txreq.gref == INVALID_GRANT_REF) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"xnf_dmamap_alloc() failed: "
"invalid grant ref");
goto error;
}
gnttab_grant_foreign_access_ref(txp->tx_txreq.gref,
oeid, txp->tx_mfn, 1);
txp->tx_txreq.offset =
dma_cookie->dmac_laddress & PAGEOFFSET;
txp->tx_txreq.size = dma_cookie->dmac_size;
txp->tx_txreq.flags = 0;
nsegs++;
if (tail != NULL)
tail->tx_txreq.flags = NETTXF_more_data;
tail = txp;
dma_cookie_prev = dma_cookie;
}
}
*countp = nsegs;
return (head);
error:
xnf_data_txbuf_free_chain(xnfp, head);
return (NULL);
}
static void
xnf_tx_setup_offload(xnf_t *xnfp, xnf_txbuf_t *head,
uint32_t cksum_flags, uint32_t lso_flags, uint32_t mss)
{
if (lso_flags != 0) {
ASSERT3U(lso_flags, ==, HW_LSO);
ASSERT3P(head->tx_bdesc, ==, NULL);
head->tx_txreq.flags |= NETTXF_extra_info;
netif_extra_info_t *extra = &head->tx_extra;
extra->type = XEN_NETIF_EXTRA_TYPE_GSO;
extra->flags = 0;
extra->u.gso.type = XEN_NETIF_GSO_TYPE_TCPV4;
extra->u.gso.size = mss;
extra->u.gso.features = 0;
extra->u.gso.pad = 0;
} else if (cksum_flags != 0) {
ASSERT3U(cksum_flags, ==, HCK_FULLCKSUM);
head->tx_txreq.flags |= NETTXF_csum_blank;
xnfp->xnf_stat_tx_cksum_deferred++;
}
}
static mblk_t *
xnf_send(void *arg, mblk_t *mp)
{
xnf_t *xnfp = arg;
xnf_txbuf_t *head;
mblk_t *ml;
int length;
int pages, chunks, slots, slots_free;
uint32_t cksum_flags, lso_flags, mss;
boolean_t pulledup = B_FALSE;
boolean_t force_copy = B_FALSE;
ASSERT3P(mp->b_next, ==, NULL);
mutex_enter(&xnfp->xnf_txlock);
while (!xnfp->xnf_connected)
cv_wait(&xnfp->xnf_cv_state, &xnfp->xnf_txlock);
slots_free = xnf_tx_slots_get(xnfp, XEN_MAX_SLOTS_PER_TX, B_FALSE);
if (slots_free < XEN_MAX_SLOTS_PER_TX) {
mutex_enter(&xnfp->xnf_schedlock);
xnfp->xnf_need_sched = B_TRUE;
mutex_exit(&xnfp->xnf_schedlock);
xnfp->xnf_stat_tx_defer++;
mutex_exit(&xnfp->xnf_txlock);
return (mp);
}
mac_hcksum_get(mp, NULL, NULL, NULL, NULL, &cksum_flags);
mac_lso_get(mp, &mss, &lso_flags);
if (cksum_flags != 0) {
ASSERT3U(cksum_flags, ==, HCK_FULLCKSUM);
(void) xnf_pseudo_cksum(mp);
}
pulledup:
for (ml = mp, pages = 0, chunks = 0, length = 0; ml != NULL;
ml = ml->b_cont, chunks++) {
pages += xnf_mblk_pages(ml);
length += MBLKL(ml);
}
DTRACE_PROBE3(packet, int, length, int, chunks, int, pages);
DTRACE_PROBE3(lso, int, length, uint32_t, lso_flags, uint32_t, mss);
if (((uintptr_t)mp->b_rptr & PAGEOFFSET) +
sizeof (struct ether_header) > PAGESIZE) {
xnfp->xnf_stat_tx_eth_hdr_split++;
if (length <= PAGESIZE)
force_copy = B_TRUE;
}
if (force_copy || (pages > 1 && !xnfp->xnf_be_tx_sg)) {
ASSERT3U(length, <=, PAGESIZE);
head = xnf_mblk_copy(xnfp, mp);
if (head == NULL) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"xnf_mblk_copy() failed");
goto drop;
}
} else {
if (pages > XEN_MAX_TX_DATA_PAGES) {
if (pulledup) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"too many pages, even after pullup: %d.",
pages);
goto drop;
}
mblk_t *newmp = msgpullup(mp, -1);
if (newmp == NULL) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"msgpullup() failed");
goto drop;
}
freemsg(mp);
mp = newmp;
xnfp->xnf_stat_tx_pullup++;
pulledup = B_TRUE;
goto pulledup;
}
head = xnf_mblk_map(xnfp, mp, &slots);
if (head == NULL)
goto drop;
IMPLY(slots > 1, xnfp->xnf_be_tx_sg);
}
head->tx_mp = mp;
xnf_tx_setup_offload(xnfp, head, cksum_flags, lso_flags, mss);
head->tx_txreq.size = length;
xnf_tx_push_packet(xnfp, head);
xnfp->xnf_stat_opackets++;
xnfp->xnf_stat_obytes += length;
mutex_exit(&xnfp->xnf_txlock);
return (NULL);
drop:
freemsg(mp);
xnfp->xnf_stat_tx_drop++;
mutex_exit(&xnfp->xnf_txlock);
return (NULL);
}
static uint_t
xnf_intr(caddr_t arg)
{
xnf_t *xnfp = (xnf_t *)arg;
mblk_t *mp;
boolean_t need_sched, clean_ring;
mutex_enter(&xnfp->xnf_rxlock);
if (!xnfp->xnf_connected) {
mutex_exit(&xnfp->xnf_rxlock);
xnfp->xnf_stat_unclaimed_interrupts++;
return (DDI_INTR_UNCLAIMED);
}
do {
xnf_rx_collect(xnfp);
xnfp->xnf_rx_ring.sring->rsp_event =
xnfp->xnf_rx_ring.rsp_cons + 1;
xen_mb();
} while (RING_HAS_UNCONSUMED_RESPONSES(&xnfp->xnf_rx_ring));
if (xnfp->xnf_rx_new_buffers_posted) {
boolean_t notify;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&xnfp->xnf_rx_ring, notify);
if (notify)
ec_notify_via_evtchn(xnfp->xnf_evtchn);
xnfp->xnf_rx_new_buffers_posted = B_FALSE;
}
mp = xnfp->xnf_rx_head;
xnfp->xnf_rx_head = xnfp->xnf_rx_tail = NULL;
xnfp->xnf_stat_interrupts++;
mutex_exit(&xnfp->xnf_rxlock);
if (mp != NULL)
mac_rx(xnfp->xnf_mh, NULL, mp);
mutex_enter(&xnfp->xnf_schedlock);
need_sched = xnfp->xnf_need_sched;
clean_ring = need_sched || (xnfp->xnf_pending_multicast > 0);
mutex_exit(&xnfp->xnf_schedlock);
if (clean_ring) {
int free_slots;
mutex_enter(&xnfp->xnf_txlock);
free_slots = xnf_tx_slots_get(xnfp, 0, B_FALSE);
if (need_sched && (free_slots >= XEN_MAX_SLOTS_PER_TX)) {
mutex_enter(&xnfp->xnf_schedlock);
xnfp->xnf_need_sched = B_FALSE;
mutex_exit(&xnfp->xnf_schedlock);
mac_tx_update(xnfp->xnf_mh);
}
mutex_exit(&xnfp->xnf_txlock);
}
return (DDI_INTR_CLAIMED);
}
static int
xnf_start(void *arg)
{
xnf_t *xnfp = arg;
mutex_enter(&xnfp->xnf_rxlock);
mutex_enter(&xnfp->xnf_txlock);
xnfp->xnf_running = B_TRUE;
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
return (0);
}
static void
xnf_stop(void *arg)
{
xnf_t *xnfp = arg;
mutex_enter(&xnfp->xnf_rxlock);
mutex_enter(&xnfp->xnf_txlock);
xnfp->xnf_running = B_FALSE;
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
}
static void
xnf_rxbuf_hang(xnf_t *xnfp, xnf_buf_t *bdesc)
{
netif_rx_request_t *reqp;
RING_IDX hang_ix;
ASSERT(MUTEX_HELD(&xnfp->xnf_rxlock));
reqp = RING_GET_REQUEST(&xnfp->xnf_rx_ring,
xnfp->xnf_rx_ring.req_prod_pvt);
hang_ix = (RING_IDX) (reqp - RING_GET_REQUEST(&xnfp->xnf_rx_ring, 0));
ASSERT(xnfp->xnf_rx_pkt_info[hang_ix] == NULL);
reqp->id = bdesc->id = hang_ix;
reqp->gref = bdesc->grant_ref;
xnfp->xnf_rx_pkt_info[hang_ix] = bdesc;
xnfp->xnf_rx_ring.req_prod_pvt++;
xnfp->xnf_rx_new_buffers_posted = B_TRUE;
}
static int
xnf_rx_one_packet(xnf_t *xnfp, RING_IDX prod, RING_IDX *consp, mblk_t **mpp)
{
mblk_t *head = NULL;
mblk_t *tail = NULL;
mblk_t *mp;
int error = 0;
RING_IDX cons = *consp;
netif_extra_info_t lro;
boolean_t is_lro = B_FALSE;
boolean_t is_extra = B_FALSE;
netif_rx_response_t rsp = *RING_GET_RESPONSE(&xnfp->xnf_rx_ring, cons);
boolean_t hwcsum = (rsp.flags & NETRXF_data_validated) != 0;
boolean_t more_data = (rsp.flags & NETRXF_more_data) != 0;
boolean_t more_extra = (rsp.flags & NETRXF_extra_info) != 0;
IMPLY(more_data, xnf_enable_rx_sg);
while (cons != prod) {
xnf_buf_t *bdesc;
int len, off;
int rxidx = cons & (NET_RX_RING_SIZE - 1);
bdesc = xnfp->xnf_rx_pkt_info[rxidx];
xnfp->xnf_rx_pkt_info[rxidx] = NULL;
if (is_extra) {
netif_extra_info_t *extra = (netif_extra_info_t *)&rsp;
if (extra->type == XEN_NETIF_EXTRA_TYPE_GSO &&
!is_lro) {
ASSERT(xnf_enable_lro);
lro = *extra;
is_lro = B_TRUE;
DTRACE_PROBE1(lro, netif_extra_info_t *, &lro);
} else {
dev_err(xnfp->xnf_devinfo, CE_WARN, "rx packet "
"contains unexpected extra info of type %d",
extra->type);
error = EINVAL;
}
more_extra =
(extra->flags & XEN_NETIF_EXTRA_FLAG_MORE) != 0;
goto hang_buf;
}
ASSERT3U(bdesc->id, ==, rsp.id);
len = rsp.status;
off = rsp.offset;
more_data = (rsp.flags & NETRXF_more_data) != 0;
if (!xnfp->xnf_running) {
error = EBUSY;
} else if (len <= 0) {
xnfp->xnf_stat_errrx++;
switch (len) {
case 0:
xnfp->xnf_stat_runt++;
break;
case NETIF_RSP_ERROR:
xnfp->xnf_stat_mac_rcv_error++;
break;
case NETIF_RSP_DROPPED:
xnfp->xnf_stat_norxbuf++;
break;
}
error = EINVAL;
} else if (bdesc->grant_ref == INVALID_GRANT_REF) {
dev_err(xnfp->xnf_devinfo, CE_WARN,
"Bad rx grant reference, rsp id %d", rsp.id);
error = EINVAL;
} else if ((off + len) > PAGESIZE) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "Rx packet crosses "
"page boundary (offset %d, length %d)", off, len);
error = EINVAL;
}
if (error != 0) {
goto hang_buf;
}
xnf_buf_t *nbuf = NULL;
if (len > xnf_rx_copy_limit)
nbuf = xnf_buf_get(xnfp, KM_NOSLEEP, B_FALSE);
if (nbuf != NULL) {
mp = desballoc((unsigned char *)bdesc->buf,
bdesc->len, 0, &bdesc->free_rtn);
if (mp == NULL) {
xnfp->xnf_stat_rx_desballoc_fail++;
xnfp->xnf_stat_norxbuf++;
error = ENOMEM;
xnf_buf_put(xnfp, nbuf, B_FALSE);
goto hang_buf;
}
mp->b_rptr = mp->b_rptr + off;
mp->b_wptr = mp->b_rptr + len;
(void) gnttab_end_foreign_access_ref(bdesc->grant_ref,
0);
xnf_gref_put(xnfp, bdesc->grant_ref);
bdesc->grant_ref = INVALID_GRANT_REF;
bdesc = nbuf;
} else {
mp = allocb(len, 0);
if (mp == NULL) {
xnfp->xnf_stat_rx_allocb_fail++;
xnfp->xnf_stat_norxbuf++;
error = ENOMEM;
goto hang_buf;
}
bcopy(bdesc->buf + off, mp->b_wptr, len);
mp->b_wptr += len;
}
if (head == NULL)
head = mp;
else
tail->b_cont = mp;
tail = mp;
hang_buf:
xnf_rxbuf_hang(xnfp, bdesc);
cons++;
is_extra = more_extra;
if (!more_data && !more_extra)
break;
rsp = *RING_GET_RESPONSE(&xnfp->xnf_rx_ring, cons);
}
ASSERT3U(*consp, !=, cons);
*consp = cons;
if (more_data) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "rx: need more fragments.");
error = EINVAL;
}
if (more_extra) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "rx: need more fragments "
"(extras).");
error = EINVAL;
}
if (error != 0) {
if (head != NULL)
freemsg(head);
xnfp->xnf_stat_rx_drop++;
return (error);
}
ASSERT(head != NULL);
if (hwcsum) {
mac_hcksum_set(head, 0, 0, 0, 0, HCK_FULLCKSUM_OK);
xnfp->xnf_stat_rx_cksum_no_need++;
}
*mpp = head;
return (0);
}
static void
xnf_rx_collect(xnf_t *xnfp)
{
RING_IDX prod;
ASSERT(MUTEX_HELD(&xnfp->xnf_rxlock));
prod = xnfp->xnf_rx_ring.sring->rsp_prod;
membar_consumer();
while (xnfp->xnf_rx_ring.rsp_cons != prod) {
mblk_t *mp;
int error = xnf_rx_one_packet(xnfp, prod,
&xnfp->xnf_rx_ring.rsp_cons, &mp);
if (error == 0) {
xnfp->xnf_stat_ipackets++;
xnfp->xnf_stat_rbytes += xmsgsize(mp);
if (xnfp->xnf_rx_head == NULL) {
ASSERT3P(xnfp->xnf_rx_tail, ==, NULL);
xnfp->xnf_rx_head = mp;
} else {
ASSERT(xnfp->xnf_rx_tail != NULL);
xnfp->xnf_rx_tail->b_next = mp;
}
xnfp->xnf_rx_tail = mp;
}
}
}
static int
xnf_alloc_dma_resources(xnf_t *xnfp)
{
dev_info_t *devinfo = xnfp->xnf_devinfo;
size_t len;
ddi_dma_cookie_t dma_cookie;
uint_t ncookies;
int rc;
caddr_t rptr;
if (ddi_dma_alloc_handle(devinfo, &ringbuf_dma_attr,
DDI_DMA_SLEEP, 0, &xnfp->xnf_tx_ring_dma_handle) != DDI_SUCCESS)
goto alloc_error;
if (ddi_dma_mem_alloc(xnfp->xnf_tx_ring_dma_handle,
PAGESIZE, &accattr, DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, 0, &rptr, &len,
&xnfp->xnf_tx_ring_dma_acchandle) != DDI_SUCCESS) {
ddi_dma_free_handle(&xnfp->xnf_tx_ring_dma_handle);
xnfp->xnf_tx_ring_dma_handle = NULL;
goto alloc_error;
}
if ((rc = ddi_dma_addr_bind_handle(xnfp->xnf_tx_ring_dma_handle, NULL,
rptr, PAGESIZE, DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, 0, &dma_cookie, &ncookies)) != DDI_DMA_MAPPED) {
ddi_dma_mem_free(&xnfp->xnf_tx_ring_dma_acchandle);
ddi_dma_free_handle(&xnfp->xnf_tx_ring_dma_handle);
xnfp->xnf_tx_ring_dma_handle = NULL;
xnfp->xnf_tx_ring_dma_acchandle = NULL;
if (rc == DDI_DMA_NORESOURCES)
goto alloc_error;
else
goto error;
}
ASSERT(ncookies == 1);
bzero(rptr, PAGESIZE);
SHARED_RING_INIT((netif_tx_sring_t *)rptr);
FRONT_RING_INIT(&xnfp->xnf_tx_ring, (netif_tx_sring_t *)rptr, PAGESIZE);
xnfp->xnf_tx_ring_phys_addr = dma_cookie.dmac_laddress;
if (ddi_dma_alloc_handle(devinfo, &ringbuf_dma_attr,
DDI_DMA_SLEEP, 0, &xnfp->xnf_rx_ring_dma_handle) != DDI_SUCCESS)
goto alloc_error;
if (ddi_dma_mem_alloc(xnfp->xnf_rx_ring_dma_handle,
PAGESIZE, &accattr, DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, 0, &rptr, &len,
&xnfp->xnf_rx_ring_dma_acchandle) != DDI_SUCCESS) {
ddi_dma_free_handle(&xnfp->xnf_rx_ring_dma_handle);
xnfp->xnf_rx_ring_dma_handle = NULL;
goto alloc_error;
}
if ((rc = ddi_dma_addr_bind_handle(xnfp->xnf_rx_ring_dma_handle, NULL,
rptr, PAGESIZE, DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, 0, &dma_cookie, &ncookies)) != DDI_DMA_MAPPED) {
ddi_dma_mem_free(&xnfp->xnf_rx_ring_dma_acchandle);
ddi_dma_free_handle(&xnfp->xnf_rx_ring_dma_handle);
xnfp->xnf_rx_ring_dma_handle = NULL;
xnfp->xnf_rx_ring_dma_acchandle = NULL;
if (rc == DDI_DMA_NORESOURCES)
goto alloc_error;
else
goto error;
}
ASSERT(ncookies == 1);
bzero(rptr, PAGESIZE);
SHARED_RING_INIT((netif_rx_sring_t *)rptr);
FRONT_RING_INIT(&xnfp->xnf_rx_ring, (netif_rx_sring_t *)rptr, PAGESIZE);
xnfp->xnf_rx_ring_phys_addr = dma_cookie.dmac_laddress;
return (DDI_SUCCESS);
alloc_error:
cmn_err(CE_WARN, "xnf%d: could not allocate enough DMA memory",
ddi_get_instance(xnfp->xnf_devinfo));
error:
xnf_release_dma_resources(xnfp);
return (DDI_FAILURE);
}
static void
xnf_release_dma_resources(xnf_t *xnfp)
{
int i;
mutex_enter(&xnfp->xnf_rxlock);
for (i = 0; i < NET_RX_RING_SIZE; i++) {
xnf_buf_t *bp;
if ((bp = xnfp->xnf_rx_pkt_info[i]) == NULL)
continue;
xnfp->xnf_rx_pkt_info[i] = NULL;
xnf_buf_put(xnfp, bp, B_FALSE);
}
mutex_exit(&xnfp->xnf_rxlock);
if (xnfp->xnf_rx_ring_dma_acchandle != NULL) {
(void) ddi_dma_unbind_handle(xnfp->xnf_rx_ring_dma_handle);
ddi_dma_mem_free(&xnfp->xnf_rx_ring_dma_acchandle);
ddi_dma_free_handle(&xnfp->xnf_rx_ring_dma_handle);
xnfp->xnf_rx_ring_dma_acchandle = NULL;
}
if (xnfp->xnf_tx_ring_dma_acchandle != NULL) {
(void) ddi_dma_unbind_handle(xnfp->xnf_tx_ring_dma_handle);
ddi_dma_mem_free(&xnfp->xnf_tx_ring_dma_acchandle);
ddi_dma_free_handle(&xnfp->xnf_tx_ring_dma_handle);
xnfp->xnf_tx_ring_dma_acchandle = NULL;
}
}
static void
xnf_release_mblks(xnf_t *xnfp)
{
RING_IDX i;
xnf_txid_t *tidp;
for (i = 0, tidp = &xnfp->xnf_tx_pkt_id[0];
i < NET_TX_RING_SIZE;
i++, tidp++) {
xnf_txbuf_t *txp = tidp->txbuf;
if (txp != NULL) {
ASSERT(txp->tx_mp != NULL);
freemsg(txp->tx_mp);
xnf_txid_put(xnfp, tidp);
kmem_cache_free(xnfp->xnf_tx_buf_cache, txp);
}
}
}
static int
xnf_buf_constructor(void *buf, void *arg, int kmflag)
{
int (*ddiflags)(caddr_t) = DDI_DMA_SLEEP;
xnf_buf_t *bdesc = buf;
xnf_t *xnfp = arg;
ddi_dma_cookie_t dma_cookie;
uint_t ncookies;
size_t len;
if (kmflag & KM_NOSLEEP)
ddiflags = DDI_DMA_DONTWAIT;
if (ddi_dma_alloc_handle(xnfp->xnf_devinfo, &rx_buf_dma_attr,
ddiflags, 0, &bdesc->dma_handle) != DDI_SUCCESS)
goto failure;
if (ddi_dma_mem_alloc(bdesc->dma_handle,
PAGESIZE, &data_accattr, DDI_DMA_STREAMING, ddiflags, 0,
&bdesc->buf, &len, &bdesc->acc_handle) != DDI_SUCCESS)
goto failure_1;
if (ddi_dma_addr_bind_handle(bdesc->dma_handle, NULL,
bdesc->buf, len, DDI_DMA_RDWR | DDI_DMA_STREAMING,
ddiflags, 0, &dma_cookie, &ncookies) != DDI_DMA_MAPPED)
goto failure_2;
ASSERT(ncookies == 1);
bdesc->free_rtn.free_func = xnf_buf_recycle;
bdesc->free_rtn.free_arg = (caddr_t)bdesc;
bdesc->xnfp = xnfp;
bdesc->buf_phys = dma_cookie.dmac_laddress;
bdesc->buf_mfn = pfn_to_mfn(xnf_btop(bdesc->buf_phys));
bdesc->len = dma_cookie.dmac_size;
bdesc->grant_ref = INVALID_GRANT_REF;
bdesc->gen = xnfp->xnf_gen;
atomic_inc_64(&xnfp->xnf_stat_buf_allocated);
return (0);
failure_2:
ddi_dma_mem_free(&bdesc->acc_handle);
failure_1:
ddi_dma_free_handle(&bdesc->dma_handle);
failure:
ASSERT(kmflag & KM_NOSLEEP);
return (-1);
}
static void
xnf_buf_destructor(void *buf, void *arg)
{
xnf_buf_t *bdesc = buf;
xnf_t *xnfp = arg;
(void) ddi_dma_unbind_handle(bdesc->dma_handle);
ddi_dma_mem_free(&bdesc->acc_handle);
ddi_dma_free_handle(&bdesc->dma_handle);
atomic_dec_64(&xnfp->xnf_stat_buf_allocated);
}
static xnf_buf_t *
xnf_buf_get(xnf_t *xnfp, int flags, boolean_t readonly)
{
grant_ref_t gref;
xnf_buf_t *bufp;
gref = xnf_gref_get(xnfp);
if (gref == INVALID_GRANT_REF)
return (NULL);
bufp = kmem_cache_alloc(xnfp->xnf_buf_cache, flags);
if (bufp == NULL) {
xnf_gref_put(xnfp, gref);
return (NULL);
}
ASSERT3U(bufp->grant_ref, ==, INVALID_GRANT_REF);
bufp->grant_ref = gref;
if (bufp->gen != xnfp->xnf_gen)
xnf_buf_refresh(bufp);
gnttab_grant_foreign_access_ref(bufp->grant_ref,
xvdi_get_oeid(bufp->xnfp->xnf_devinfo),
bufp->buf_mfn, readonly ? 1 : 0);
atomic_inc_64(&xnfp->xnf_stat_buf_outstanding);
return (bufp);
}
static void
xnf_buf_put(xnf_t *xnfp, xnf_buf_t *bufp, boolean_t readonly)
{
if (bufp->grant_ref != INVALID_GRANT_REF) {
(void) gnttab_end_foreign_access_ref(
bufp->grant_ref, readonly ? 1 : 0);
xnf_gref_put(xnfp, bufp->grant_ref);
bufp->grant_ref = INVALID_GRANT_REF;
}
kmem_cache_free(xnfp->xnf_buf_cache, bufp);
atomic_dec_64(&xnfp->xnf_stat_buf_outstanding);
}
static void
xnf_buf_refresh(xnf_buf_t *bdesc)
{
bdesc->buf_mfn = pfn_to_mfn(xnf_btop(bdesc->buf_phys));
bdesc->gen = bdesc->xnfp->xnf_gen;
}
static void
xnf_buf_recycle(xnf_buf_t *bdesc)
{
xnf_t *xnfp = bdesc->xnfp;
xnf_buf_put(xnfp, bdesc, B_TRUE);
}
static int
xnf_tx_buf_constructor(void *buf, void *arg, int kmflag)
{
int (*ddiflags)(caddr_t) = DDI_DMA_SLEEP;
xnf_txbuf_t *txp = buf;
xnf_t *xnfp = arg;
if (kmflag & KM_NOSLEEP)
ddiflags = DDI_DMA_DONTWAIT;
if (ddi_dma_alloc_handle(xnfp->xnf_devinfo, &tx_buf_dma_attr,
ddiflags, 0, &txp->tx_dma_handle) != DDI_SUCCESS) {
ASSERT(kmflag & KM_NOSLEEP);
return (-1);
}
return (0);
}
static void
xnf_tx_buf_destructor(void *buf, void *arg)
{
_NOTE(ARGUNUSED(arg));
xnf_txbuf_t *txp = buf;
ddi_dma_free_handle(&txp->tx_dma_handle);
}
static char *xnf_aux_statistics[] = {
"tx_cksum_deferred",
"rx_cksum_no_need",
"interrupts",
"unclaimed_interrupts",
"tx_pullup",
"tx_lookaside",
"tx_drop",
"tx_eth_hdr_split",
"buf_allocated",
"buf_outstanding",
"gref_outstanding",
"gref_failure",
"gref_peak",
"rx_allocb_fail",
"rx_desballoc_fail",
};
static int
xnf_kstat_aux_update(kstat_t *ksp, int flag)
{
xnf_t *xnfp;
kstat_named_t *knp;
if (flag != KSTAT_READ)
return (EACCES);
xnfp = ksp->ks_private;
knp = ksp->ks_data;
(knp++)->value.ui64 = xnfp->xnf_stat_tx_cksum_deferred;
(knp++)->value.ui64 = xnfp->xnf_stat_rx_cksum_no_need;
(knp++)->value.ui64 = xnfp->xnf_stat_interrupts;
(knp++)->value.ui64 = xnfp->xnf_stat_unclaimed_interrupts;
(knp++)->value.ui64 = xnfp->xnf_stat_tx_pullup;
(knp++)->value.ui64 = xnfp->xnf_stat_tx_lookaside;
(knp++)->value.ui64 = xnfp->xnf_stat_tx_drop;
(knp++)->value.ui64 = xnfp->xnf_stat_tx_eth_hdr_split;
(knp++)->value.ui64 = xnfp->xnf_stat_buf_allocated;
(knp++)->value.ui64 = xnfp->xnf_stat_buf_outstanding;
(knp++)->value.ui64 = xnfp->xnf_stat_gref_outstanding;
(knp++)->value.ui64 = xnfp->xnf_stat_gref_failure;
(knp++)->value.ui64 = xnfp->xnf_stat_gref_peak;
(knp++)->value.ui64 = xnfp->xnf_stat_rx_allocb_fail;
(knp++)->value.ui64 = xnfp->xnf_stat_rx_desballoc_fail;
return (0);
}
static boolean_t
xnf_kstat_init(xnf_t *xnfp)
{
int nstat = sizeof (xnf_aux_statistics) /
sizeof (xnf_aux_statistics[0]);
char **cp = xnf_aux_statistics;
kstat_named_t *knp;
if ((xnfp->xnf_kstat_aux = kstat_create("xnf",
ddi_get_instance(xnfp->xnf_devinfo),
"aux_statistics", "net", KSTAT_TYPE_NAMED,
nstat, 0)) == NULL)
return (B_FALSE);
xnfp->xnf_kstat_aux->ks_private = xnfp;
xnfp->xnf_kstat_aux->ks_update = xnf_kstat_aux_update;
knp = xnfp->xnf_kstat_aux->ks_data;
while (nstat > 0) {
kstat_named_init(knp, *cp, KSTAT_DATA_UINT64);
knp++;
cp++;
nstat--;
}
kstat_install(xnfp->xnf_kstat_aux);
return (B_TRUE);
}
static int
xnf_stat(void *arg, uint_t stat, uint64_t *val)
{
xnf_t *xnfp = arg;
mutex_enter(&xnfp->xnf_rxlock);
mutex_enter(&xnfp->xnf_txlock);
#define mac_stat(q, r) \
case (MAC_STAT_##q): \
*val = xnfp->xnf_stat_##r; \
break
#define ether_stat(q, r) \
case (ETHER_STAT_##q): \
*val = xnfp->xnf_stat_##r; \
break
switch (stat) {
mac_stat(IPACKETS, ipackets);
mac_stat(OPACKETS, opackets);
mac_stat(RBYTES, rbytes);
mac_stat(OBYTES, obytes);
mac_stat(NORCVBUF, norxbuf);
mac_stat(IERRORS, errrx);
mac_stat(NOXMTBUF, tx_defer);
ether_stat(MACRCV_ERRORS, mac_rcv_error);
ether_stat(TOOSHORT_ERRORS, runt);
case ETHER_STAT_LINK_DUPLEX:
*val = LINK_DUPLEX_FULL;
break;
case MAC_STAT_IFSPEED:
*val = 1000000000ull;
break;
default:
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
return (ENOTSUP);
}
#undef mac_stat
#undef ether_stat
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
return (0);
}
static int
xnf_change_mtu(xnf_t *xnfp, uint32_t mtu)
{
if (mtu > ETHERMTU) {
if (!xnf_enable_tx_sg) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "MTU limited to %d "
"because scatter-gather is disabled for transmit "
"in driver settings", ETHERMTU);
return (EINVAL);
} else if (!xnf_enable_rx_sg) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "MTU limited to %d "
"because scatter-gather is disabled for receive "
"in driver settings", ETHERMTU);
return (EINVAL);
} else if (!xnfp->xnf_be_tx_sg) {
dev_err(xnfp->xnf_devinfo, CE_WARN, "MTU limited to %d "
"because backend doesn't support scatter-gather",
ETHERMTU);
return (EINVAL);
}
if (mtu > XNF_MAXPKT)
return (EINVAL);
}
int error = mac_maxsdu_update(xnfp->xnf_mh, mtu);
if (error == 0)
xnfp->xnf_mtu = mtu;
return (error);
}
static int
xnf_getprop(void *data, const char *prop_name, mac_prop_id_t prop_id,
uint_t prop_val_size, void *prop_val)
{
xnf_t *xnfp = data;
switch (prop_id) {
case MAC_PROP_MTU:
ASSERT(prop_val_size >= sizeof (uint32_t));
bcopy(&xnfp->xnf_mtu, prop_val, sizeof (uint32_t));
break;
default:
return (ENOTSUP);
}
return (0);
}
static int
xnf_setprop(void *data, const char *prop_name, mac_prop_id_t prop_id,
uint_t prop_val_size, const void *prop_val)
{
xnf_t *xnfp = data;
uint32_t new_mtu;
int error;
switch (prop_id) {
case MAC_PROP_MTU:
ASSERT(prop_val_size >= sizeof (uint32_t));
bcopy(prop_val, &new_mtu, sizeof (new_mtu));
error = xnf_change_mtu(xnfp, new_mtu);
break;
default:
return (ENOTSUP);
}
return (error);
}
static void
xnf_propinfo(void *data, const char *prop_name, mac_prop_id_t prop_id,
mac_prop_info_handle_t prop_handle)
{
switch (prop_id) {
case MAC_PROP_MTU:
mac_prop_info_set_range_uint32(prop_handle, 0, XNF_MAXPKT);
break;
default:
break;
}
}
static boolean_t
xnf_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
xnf_t *xnfp = arg;
switch (cap) {
case MAC_CAPAB_HCKSUM: {
uint32_t *capab = cap_data;
*capab = HCKSUM_INET_FULL_V4;
break;
}
case MAC_CAPAB_LSO: {
if (!xnfp->xnf_be_lso)
return (B_FALSE);
mac_capab_lso_t *lso = cap_data;
lso->lso_flags = LSO_TX_BASIC_TCP_IPV4;
lso->lso_basic_tcp_ipv4.lso_max = IP_MAXPACKET;
break;
}
default:
return (B_FALSE);
}
return (B_TRUE);
}
static void
oe_state_change(dev_info_t *dip, ddi_eventcookie_t id,
void *arg, void *impl_data)
{
_NOTE(ARGUNUSED(id, arg));
xnf_t *xnfp = ddi_get_driver_private(dip);
XenbusState new_state = *(XenbusState *)impl_data;
ASSERT(xnfp != NULL);
switch (new_state) {
case XenbusStateUnknown:
case XenbusStateInitialising:
case XenbusStateInitialised:
case XenbusStateClosing:
case XenbusStateClosed:
case XenbusStateReconfiguring:
case XenbusStateReconfigured:
break;
case XenbusStateInitWait:
xnf_read_config(xnfp);
if (!xnfp->xnf_be_rx_copy) {
cmn_err(CE_WARN,
"The xnf driver requires a dom0 that "
"supports 'feature-rx-copy'.");
(void) xvdi_switch_state(xnfp->xnf_devinfo,
XBT_NULL, XenbusStateClosed);
break;
}
xnf_be_connect(xnfp);
mac_unicst_update(xnfp->xnf_mh, xnfp->xnf_mac_addr);
mac_capab_update(xnfp->xnf_mh);
break;
case XenbusStateConnected:
mutex_enter(&xnfp->xnf_rxlock);
mutex_enter(&xnfp->xnf_txlock);
xnfp->xnf_connected = B_TRUE;
cv_broadcast(&xnfp->xnf_cv_state);
mutex_exit(&xnfp->xnf_txlock);
mutex_exit(&xnfp->xnf_rxlock);
ec_notify_via_evtchn(xnfp->xnf_evtchn);
(void) xnf_intr((caddr_t)xnfp);
mac_link_update(xnfp->xnf_mh, LINK_STATE_UP);
mac_multicast_refresh(xnfp->xnf_mh, NULL, xnfp, B_TRUE);
break;
default:
break;
}
}