#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_xennet_xenbus.c,v 1.132 2025/08/28 05:59:16 mrg Exp $");
#include "opt_xen.h"
#include "opt_nfs_boot.h"
#include "opt_net_mpsafe.h"
#include <sys/param.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/intr.h>
#include <sys/rndsource.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#if defined(NFS_BOOT_BOOTSTATIC)
#include <sys/fstypes.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <netinet/in.h>
#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfs/nfs.h>
#include <nfs/nfsmount.h>
#include <nfs/nfsdiskless.h>
#include <xen/if_xennetvar.h>
#endif
#include <xen/xennet_checksum.h>
#include <uvm/uvm.h>
#include <xen/intr.h>
#include <xen/hypervisor.h>
#include <xen/evtchn.h>
#include <xen/granttables.h>
#include <xen/include/public/io/netif.h>
#include <xen/xenpmap.h>
#include <xen/xenbus.h>
#include "locators.h"
#undef XENNET_DEBUG_DUMP
#undef XENNET_DEBUG
#ifdef XENNET_DEBUG
#define XEDB_FOLLOW 0x01
#define XEDB_INIT 0x02
#define XEDB_EVENT 0x04
#define XEDB_MBUF 0x08
#define XEDB_MEM 0x10
int xennet_debug = 0xff;
#define DPRINTF(x) if (xennet_debug) printf x;
#define DPRINTFN(n,x) if (xennet_debug & (n)) printf x;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
#define GRANT_INVALID_REF -1
#define NET_TX_RING_SIZE __CONST_RING_SIZE(netif_tx, PAGE_SIZE)
#define NET_RX_RING_SIZE __CONST_RING_SIZE(netif_rx, PAGE_SIZE)
struct xennet_txreq {
SLIST_ENTRY(xennet_txreq) txreq_next;
uint16_t txreq_id;
grant_ref_t txreq_gntref;
struct mbuf *txreq_m;
bus_dmamap_t txreq_dmamap;
};
struct xennet_rxreq {
SLIST_ENTRY(xennet_rxreq) rxreq_next;
uint16_t rxreq_id;
grant_ref_t rxreq_gntref;
struct mbuf *rxreq_m;
bus_dmamap_t rxreq_dmamap;
};
struct xennet_xenbus_softc {
device_t sc_dev;
struct ethercom sc_ethercom;
uint8_t sc_enaddr[ETHER_ADDR_LEN];
struct xenbus_device *sc_xbusd;
netif_tx_front_ring_t sc_tx_ring;
netif_rx_front_ring_t sc_rx_ring;
unsigned int sc_evtchn;
struct intrhand *sc_ih;
grant_ref_t sc_tx_ring_gntref;
grant_ref_t sc_rx_ring_gntref;
kmutex_t sc_tx_lock;
kmutex_t sc_rx_lock;
struct xennet_txreq sc_txreqs[NET_TX_RING_SIZE];
struct xennet_rxreq sc_rxreqs[NET_RX_RING_SIZE];
SLIST_HEAD(,xennet_txreq) sc_txreq_head;
SLIST_HEAD(,xennet_rxreq) sc_rxreq_head;
int sc_free_txreql;
int sc_free_rxreql;
int sc_backend_status;
#define BEST_CLOSED 0
#define BEST_DISCONNECTED 1
#define BEST_CONNECTED 2
#define BEST_SUSPENDED 3
int sc_features;
#define FEATURE_IPV6CSUM 0x01
#define FEATURE_SG 0x02
#define FEATURE_RX_COPY 0x04
#define FEATURE_BITS "\20\1IPV6-CSUM\2SG\3RX-COPY"
krndsource_t sc_rnd_source;
struct evcnt sc_cnt_tx_defrag;
struct evcnt sc_cnt_tx_queue_full;
struct evcnt sc_cnt_tx_drop;
struct evcnt sc_cnt_tx_frag;
struct evcnt sc_cnt_rx_frag;
struct evcnt sc_cnt_rx_cksum_blank;
struct evcnt sc_cnt_rx_cksum_undefer;
};
static struct sysctllog *xennet_log;
static int xennet_xnfrx_nodenum;
#ifndef XENNET_XNFRX_LOWAT
#define XENNET_XNFRX_LOWAT 0
#endif
#define IF_XNFRX_LOWAT_MAX 128
static int if_xnfrx_lowat = XENNET_XNFRX_LOWAT;
static pool_cache_t if_xennetrxbuf_cache;
static int if_xennetrxbuf_cache_inited = 0;
static int xennet_xenbus_match(device_t, cfdata_t, void *);
static void xennet_xenbus_attach(device_t, device_t, void *);
static int xennet_xenbus_detach(device_t, int);
static void xennet_backend_changed(void *, XenbusState);
static void xennet_alloc_rx_buffer(struct xennet_xenbus_softc *);
static void xennet_free_rx_buffer(struct xennet_xenbus_softc *, bool);
static void xennet_tx_complete(struct xennet_xenbus_softc *);
static void xennet_rx_mbuf_free(struct mbuf *, void *, size_t, void *);
static int xennet_handler(void *);
static bool xennet_talk_to_backend(struct xennet_xenbus_softc *);
#ifdef XENNET_DEBUG_DUMP
static void xennet_hex_dump(const unsigned char *, size_t, const char *, int);
#endif
static void xennet_sysctl_init(struct xennet_xenbus_softc *);
static int xennet_sysctl_verify(SYSCTLFN_PROTO);
static int xennet_init(struct ifnet *);
static void xennet_stop(struct ifnet *, int);
static void xennet_start(struct ifnet *);
static int xennet_ioctl(struct ifnet *, u_long, void *);
static bool xennet_xenbus_suspend(device_t dev, const pmf_qual_t *);
static bool xennet_xenbus_resume(device_t dev, const pmf_qual_t *);
CFATTACH_DECL3_NEW(xennet, sizeof(struct xennet_xenbus_softc),
xennet_xenbus_match, xennet_xenbus_attach, xennet_xenbus_detach, NULL,
NULL, NULL, DVF_DETACH_SHUTDOWN);
static int
xennet_xenbus_match(device_t parent, cfdata_t match, void *aux)
{
struct xenbusdev_attach_args *xa = aux;
if (strcmp(xa->xa_type, "vif") != 0)
return 0;
if (match->cf_loc[XENBUSCF_ID] != XENBUSCF_ID_DEFAULT &&
match->cf_loc[XENBUSCF_ID] != xa->xa_id)
return 0;
return 1;
}
static void
xennet_xenbus_attach(device_t parent, device_t self, void *aux)
{
struct xennet_xenbus_softc *sc = device_private(self);
struct xenbusdev_attach_args *xa = aux;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
int err;
netif_tx_sring_t *tx_ring;
netif_rx_sring_t *rx_ring;
RING_IDX i;
char *e, *p;
unsigned long uval;
extern int ifqmaxlen;
char mac[32];
char buf[64];
bus_size_t maxsz;
int nsegs;
aprint_normal(": Xen Virtual Network Interface\n");
sc->sc_dev = self;
sc->sc_xbusd = xa->xa_xbusd;
sc->sc_xbusd->xbusd_otherend_changed = xennet_backend_changed;
err = xenbus_read_ul(NULL, sc->sc_xbusd->xbusd_otherend,
"feature-rx-copy", &uval, 10);
if (!err && uval == 1)
sc->sc_features |= FEATURE_RX_COPY;
err = xenbus_read_ul(NULL, sc->sc_xbusd->xbusd_otherend,
"feature-ipv6-csum-offload", &uval, 10);
if (!err && uval == 1)
sc->sc_features |= FEATURE_IPV6CSUM;
err = xenbus_read_ul(NULL, sc->sc_xbusd->xbusd_otherend,
"feature-sg", &uval, 10);
if (!err && uval == 1)
sc->sc_features |= FEATURE_SG;
snprintb(buf, sizeof(buf), FEATURE_BITS, sc->sc_features);
aprint_normal_dev(sc->sc_dev, "backend features %s\n", buf);
if (if_xennetrxbuf_cache_inited == 0) {
if_xennetrxbuf_cache = pool_cache_init(PAGE_SIZE, 0, 0, 0,
"xnfrx", NULL, IPL_NET, NULL, NULL, NULL);
if (if_xnfrx_lowat) {
pool_cache_setlowat(if_xennetrxbuf_cache, if_xnfrx_lowat);
}
xennet_sysctl_init(sc);
if_xennetrxbuf_cache_inited = 1;
}
if (sc->sc_features & FEATURE_SG) {
maxsz = ETHER_MAX_LEN_JUMBO;
nsegs = XEN_NETIF_NR_SLOTS_MIN;
} else {
maxsz = PAGE_SIZE;
nsegs = 1;
}
mutex_init(&sc->sc_tx_lock, MUTEX_DEFAULT, IPL_NET);
SLIST_INIT(&sc->sc_txreq_head);
for (i = 0; i < NET_TX_RING_SIZE; i++) {
struct xennet_txreq *txreq = &sc->sc_txreqs[i];
txreq->txreq_id = i;
if (bus_dmamap_create(sc->sc_xbusd->xbusd_dmat, maxsz, nsegs,
PAGE_SIZE, PAGE_SIZE, BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW,
&txreq->txreq_dmamap) != 0)
break;
SLIST_INSERT_HEAD(&sc->sc_txreq_head, &sc->sc_txreqs[i],
txreq_next);
}
sc->sc_free_txreql = i;
mutex_init(&sc->sc_rx_lock, MUTEX_DEFAULT, IPL_NET);
SLIST_INIT(&sc->sc_rxreq_head);
for (i = 0; i < NET_RX_RING_SIZE; i++) {
struct xennet_rxreq *rxreq = &sc->sc_rxreqs[i];
rxreq->rxreq_id = i;
if (bus_dmamap_create(sc->sc_xbusd->xbusd_dmat, maxsz, nsegs,
PAGE_SIZE, PAGE_SIZE, BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW,
&rxreq->rxreq_dmamap) != 0)
break;
rxreq->rxreq_gntref = GRANT_INVALID_REF;
SLIST_INSERT_HEAD(&sc->sc_rxreq_head, rxreq, rxreq_next);
}
sc->sc_free_rxreql = i;
if (sc->sc_free_rxreql == 0) {
aprint_error_dev(self, "failed to allocate rx memory\n");
return;
}
err = xenbus_read(NULL, sc->sc_xbusd->xbusd_path, "mac",
mac, sizeof(mac));
if (err) {
aprint_error_dev(self, "can't read mac address, err %d\n", err);
return;
}
for (i = 0, p = mac; i < ETHER_ADDR_LEN; i++) {
sc->sc_enaddr[i] = strtoul(p, &e, 16);
if ((e[0] == '\0' && i != 5) && e[0] != ':') {
aprint_error_dev(self,
"%s is not a valid mac address\n", mac);
return;
}
p = &e[1];
}
aprint_normal_dev(self, "MAC address %s\n",
ether_sprintf(sc->sc_enaddr));
strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ);
sc->sc_ethercom.ec_capabilities |= ETHERCAP_VLAN_MTU;
if (sc->sc_features & FEATURE_SG)
sc->sc_ethercom.ec_capabilities |= ETHERCAP_JUMBO_MTU;
ifp->if_softc = sc;
ifp->if_start = xennet_start;
ifp->if_ioctl = xennet_ioctl;
ifp->if_init = xennet_init;
ifp->if_stop = xennet_stop;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_extflags = IFEF_MPSAFE;
ifp->if_snd.ifq_maxlen = uimax(ifqmaxlen, NET_TX_RING_SIZE * 2);
ifp->if_capabilities =
IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv4_Tx
| IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv4_Tx
| IFCAP_CSUM_UDPv6_Rx
| IFCAP_CSUM_TCPv6_Rx;
#define XN_M_CSUM_SUPPORTED \
(M_CSUM_TCPv4 | M_CSUM_UDPv4 | M_CSUM_TCPv6 | M_CSUM_UDPv6)
if (sc->sc_features & FEATURE_IPV6CSUM) {
ifp->if_capabilities |=
IFCAP_CSUM_UDPv6_Tx | IFCAP_CSUM_TCPv6_Tx;
}
IFQ_SET_MAXLEN(&ifp->if_snd, uimax(2 * NET_TX_RING_SIZE, IFQ_MAXLEN));
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, sc->sc_enaddr);
tx_ring = (void *)uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
UVM_KMF_WIRED);
rx_ring = (void *)uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
UVM_KMF_WIRED);
if (tx_ring == NULL || rx_ring == NULL)
panic("%s: can't alloc rings", device_xname(self));
sc->sc_tx_ring.sring = tx_ring;
sc->sc_rx_ring.sring = rx_ring;
rnd_attach_source(&sc->sc_rnd_source, device_xname(sc->sc_dev),
RND_TYPE_NET, RND_FLAG_DEFAULT);
evcnt_attach_dynamic(&sc->sc_cnt_tx_defrag, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Tx packet defrag");
evcnt_attach_dynamic(&sc->sc_cnt_tx_frag, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Tx multi-segment packet");
evcnt_attach_dynamic(&sc->sc_cnt_tx_drop, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Tx packet dropped");
evcnt_attach_dynamic(&sc->sc_cnt_tx_queue_full, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Tx queue full");
evcnt_attach_dynamic(&sc->sc_cnt_rx_frag, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Rx multi-segment packet");
evcnt_attach_dynamic(&sc->sc_cnt_rx_cksum_blank, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Rx csum blank");
evcnt_attach_dynamic(&sc->sc_cnt_rx_cksum_undefer, EVCNT_TYPE_MISC,
NULL, device_xname(sc->sc_dev), "Rx csum undeferred");
if (!pmf_device_register(self, xennet_xenbus_suspend,
xennet_xenbus_resume))
aprint_error_dev(self, "couldn't establish power handler\n");
else
pmf_class_network_register(self, ifp);
if (xennet_xenbus_resume(self, PMF_Q_NONE) == false) {
uvm_km_free(kernel_map, (vaddr_t)tx_ring, PAGE_SIZE,
UVM_KMF_WIRED);
uvm_km_free(kernel_map, (vaddr_t)rx_ring, PAGE_SIZE,
UVM_KMF_WIRED);
return;
}
}
static int
xennet_xenbus_detach(device_t self, int flags)
{
struct xennet_xenbus_softc *sc = device_private(self);
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
if ((flags & (DETACH_SHUTDOWN | DETACH_FORCE)) == DETACH_SHUTDOWN) {
xenbus_switch_state(sc->sc_xbusd, NULL, XenbusStateClosing);
return EBUSY;
}
DPRINTF(("%s: xennet_xenbus_detach\n", device_xname(self)));
IFNET_LOCK(ifp);
xennet_stop(ifp, 1);
IFNET_UNLOCK(ifp);
if (sc->sc_ih != NULL) {
xen_intr_disestablish(sc->sc_ih);
sc->sc_ih = NULL;
}
mutex_enter(&sc->sc_tx_lock);
xennet_tx_complete(sc);
while (sc->sc_tx_ring.sring->rsp_prod != sc->sc_tx_ring.rsp_cons) {
kpause("xndetach", true, hz/2, &sc->sc_tx_lock);
xennet_tx_complete(sc);
}
mutex_exit(&sc->sc_tx_lock);
mutex_enter(&sc->sc_rx_lock);
xennet_free_rx_buffer(sc, true);
mutex_exit(&sc->sc_rx_lock);
ether_ifdetach(ifp);
if_detach(ifp);
evcnt_detach(&sc->sc_cnt_tx_defrag);
evcnt_detach(&sc->sc_cnt_tx_frag);
evcnt_detach(&sc->sc_cnt_tx_drop);
evcnt_detach(&sc->sc_cnt_tx_queue_full);
evcnt_detach(&sc->sc_cnt_rx_frag);
evcnt_detach(&sc->sc_cnt_rx_cksum_blank);
evcnt_detach(&sc->sc_cnt_rx_cksum_undefer);
rnd_detach_source(&sc->sc_rnd_source);
mutex_enter(&sc->sc_tx_lock);
while (xengnt_status(sc->sc_tx_ring_gntref))
kpause("xntxref", true, hz/2, &sc->sc_tx_lock);
xengnt_revoke_access(sc->sc_tx_ring_gntref);
mutex_exit(&sc->sc_tx_lock);
uvm_km_free(kernel_map, (vaddr_t)sc->sc_tx_ring.sring, PAGE_SIZE,
UVM_KMF_WIRED);
mutex_enter(&sc->sc_rx_lock);
while (xengnt_status(sc->sc_rx_ring_gntref))
kpause("xnrxref", true, hz/2, &sc->sc_rx_lock);
xengnt_revoke_access(sc->sc_rx_ring_gntref);
mutex_exit(&sc->sc_rx_lock);
uvm_km_free(kernel_map, (vaddr_t)sc->sc_rx_ring.sring, PAGE_SIZE,
UVM_KMF_WIRED);
pmf_device_deregister(self);
sc->sc_backend_status = BEST_DISCONNECTED;
DPRINTF(("%s: xennet_xenbus_detach done\n", device_xname(self)));
return 0;
}
static bool
xennet_xenbus_resume(device_t dev, const pmf_qual_t *qual)
{
struct xennet_xenbus_softc *sc = device_private(dev);
int error;
netif_tx_sring_t *tx_ring;
netif_rx_sring_t *rx_ring;
paddr_t ma;
sc->sc_tx_ring_gntref = GRANT_INVALID_REF;
sc->sc_rx_ring_gntref = GRANT_INVALID_REF;
mutex_enter(&sc->sc_rx_lock);
xennet_free_rx_buffer(sc, false);
KASSERT(sc->sc_free_rxreql == NET_TX_RING_SIZE);
mutex_exit(&sc->sc_rx_lock);
tx_ring = sc->sc_tx_ring.sring;
rx_ring = sc->sc_rx_ring.sring;
memset(tx_ring, 0, PAGE_SIZE);
SHARED_RING_INIT(tx_ring);
FRONT_RING_INIT(&sc->sc_tx_ring, tx_ring, PAGE_SIZE);
memset(rx_ring, 0, PAGE_SIZE);
SHARED_RING_INIT(rx_ring);
FRONT_RING_INIT(&sc->sc_rx_ring, rx_ring, PAGE_SIZE);
(void)pmap_extract_ma(pmap_kernel(), (vaddr_t)tx_ring, &ma);
error = xenbus_grant_ring(sc->sc_xbusd, ma, &sc->sc_tx_ring_gntref);
if (error)
goto abort_resume;
(void)pmap_extract_ma(pmap_kernel(), (vaddr_t)rx_ring, &ma);
error = xenbus_grant_ring(sc->sc_xbusd, ma, &sc->sc_rx_ring_gntref);
if (error)
goto abort_resume;
if (sc->sc_ih != NULL) {
xen_intr_disestablish(sc->sc_ih);
sc->sc_ih = NULL;
}
error = xenbus_alloc_evtchn(sc->sc_xbusd, &sc->sc_evtchn);
if (error)
goto abort_resume;
aprint_verbose_dev(dev, "using event channel %d\n",
sc->sc_evtchn);
sc->sc_ih = xen_intr_establish_xname(-1, &xen_pic, sc->sc_evtchn,
IST_LEVEL, IPL_NET, &xennet_handler, sc, true, device_xname(dev));
KASSERT(sc->sc_ih != NULL);
mutex_enter(&sc->sc_rx_lock);
xennet_alloc_rx_buffer(sc);
KASSERT(sc->sc_free_rxreql == 0);
mutex_exit(&sc->sc_rx_lock);
xenbus_switch_state(sc->sc_xbusd, NULL, XenbusStateInitialised);
if (sc->sc_backend_status == BEST_SUSPENDED) {
if (xennet_talk_to_backend(sc)) {
xenbus_device_resume(sc->sc_xbusd);
hypervisor_unmask_event(sc->sc_evtchn);
xenbus_switch_state(sc->sc_xbusd, NULL,
XenbusStateConnected);
}
}
return true;
abort_resume:
xenbus_dev_fatal(sc->sc_xbusd, error, "resuming device");
return false;
}
static bool
xennet_talk_to_backend(struct xennet_xenbus_softc *sc)
{
int error;
struct xenbus_transaction *xbt;
const char *errmsg;
again:
xbt = xenbus_transaction_start();
if (xbt == NULL)
return false;
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"vifname", "%s", device_xname(sc->sc_dev));
if (error) {
errmsg = "vifname";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"tx-ring-ref","%u", sc->sc_tx_ring_gntref);
if (error) {
errmsg = "writing tx ring-ref";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"rx-ring-ref","%u", sc->sc_rx_ring_gntref);
if (error) {
errmsg = "writing rx ring-ref";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"request-rx-copy", "%u", 1);
if (error) {
errmsg = "writing request-rx-copy";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"feature-rx-notify", "%u", 1);
if (error) {
errmsg = "writing feature-rx-notify";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"feature-ipv6-csum-offload", "%u", 1);
if (error) {
errmsg = "writing feature-ipv6-csum-offload";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"feature-sg", "%u", 1);
if (error) {
errmsg = "writing feature-sg";
goto abort_transaction;
}
error = xenbus_printf(xbt, sc->sc_xbusd->xbusd_path,
"event-channel", "%u", sc->sc_evtchn);
if (error) {
errmsg = "writing event channel";
goto abort_transaction;
}
error = xenbus_transaction_end(xbt, 0);
if (error == EAGAIN)
goto again;
if (error) {
xenbus_dev_fatal(sc->sc_xbusd, error, "completing transaction");
return false;
}
mutex_enter(&sc->sc_rx_lock);
xennet_alloc_rx_buffer(sc);
mutex_exit(&sc->sc_rx_lock);
sc->sc_backend_status = BEST_CONNECTED;
return true;
abort_transaction:
xenbus_transaction_end(xbt, 1);
xenbus_dev_fatal(sc->sc_xbusd, error, "%s", errmsg);
return false;
}
static bool
xennet_xenbus_suspend(device_t dev, const pmf_qual_t *qual)
{
struct xennet_xenbus_softc *sc = device_private(dev);
mutex_enter(&sc->sc_tx_lock);
xennet_tx_complete(sc);
while (sc->sc_tx_ring.sring->rsp_prod != sc->sc_tx_ring.rsp_cons) {
kpause("xnsuspend", true, hz/2, &sc->sc_tx_lock);
xennet_tx_complete(sc);
}
KASSERT(sc->sc_free_txreql == NET_RX_RING_SIZE);
mutex_exit(&sc->sc_tx_lock);
sc->sc_backend_status = BEST_SUSPENDED;
xenbus_device_suspend(sc->sc_xbusd);
aprint_verbose_dev(dev, "removed event channel %d\n", sc->sc_evtchn);
return true;
}
static void xennet_backend_changed(void *arg, XenbusState new_state)
{
struct xennet_xenbus_softc *sc = device_private((device_t)arg);
DPRINTF(("%s: new backend state %d\n",
device_xname(sc->sc_dev), new_state));
switch (new_state) {
case XenbusStateInitialising:
case XenbusStateInitialised:
case XenbusStateConnected:
break;
case XenbusStateClosing:
sc->sc_backend_status = BEST_CLOSED;
xenbus_switch_state(sc->sc_xbusd, NULL, XenbusStateClosed);
break;
case XenbusStateInitWait:
if (sc->sc_backend_status == BEST_CONNECTED
|| sc->sc_backend_status == BEST_SUSPENDED)
break;
if (xennet_talk_to_backend(sc))
xenbus_switch_state(sc->sc_xbusd, NULL,
XenbusStateConnected);
break;
case XenbusStateUnknown:
default:
panic("bad backend state %d", new_state);
}
}
static void
xennet_alloc_rx_buffer(struct xennet_xenbus_softc *sc)
{
RING_IDX req_prod = sc->sc_rx_ring.req_prod_pvt;
RING_IDX i;
netif_rx_request_t *rxreq;
struct xennet_rxreq *req;
int otherend_id, notify;
struct mbuf *m;
vaddr_t va;
paddr_t pa, ma;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
KASSERT(mutex_owned(&sc->sc_rx_lock));
otherend_id = sc->sc_xbusd->xbusd_otherend_id;
for (i = 0; sc->sc_free_rxreql != 0; i++) {
req = SLIST_FIRST(&sc->sc_rxreq_head);
KASSERT(req != NULL);
KASSERT(req == &sc->sc_rxreqs[req->rxreq_id]);
KASSERT(req->rxreq_m == NULL);
KASSERT(req->rxreq_gntref == GRANT_INVALID_REF);
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (__predict_false(m == NULL)) {
printf("%s: rx no mbuf\n", ifp->if_xname);
break;
}
va = (vaddr_t)pool_cache_get_paddr(
if_xennetrxbuf_cache, PR_NOWAIT, &pa);
if (__predict_false(va == 0)) {
printf("%s: rx no cluster\n", ifp->if_xname);
m_freem(m);
break;
}
MEXTADD(m, va, PAGE_SIZE,
M_DEVBUF, xennet_rx_mbuf_free, NULL);
m->m_len = m->m_pkthdr.len = PAGE_SIZE;
m->m_ext.ext_paddr = pa;
m->m_flags |= M_EXT_RW;
m->m_flags |= M_EXT_CLUSTER;
if (__predict_false(bus_dmamap_load_mbuf(
sc->sc_xbusd->xbusd_dmat,
req->rxreq_dmamap, m, BUS_DMA_NOWAIT) != 0)) {
printf("%s: rx mbuf load failed", ifp->if_xname);
m->m_flags &= ~M_EXT_CLUSTER;
m_freem(m);
break;
}
m->m_flags &= ~M_EXT_CLUSTER;
KASSERT(req->rxreq_dmamap->dm_nsegs == 1);
ma = req->rxreq_dmamap->dm_segs[0].ds_addr;
if (xengnt_grant_access(otherend_id, trunc_page(ma),
0, &req->rxreq_gntref) != 0) {
m_freem(m);
break;
}
req->rxreq_m = m;
rxreq = RING_GET_REQUEST(&sc->sc_rx_ring, req_prod + i);
rxreq->id = req->rxreq_id;
rxreq->gref = req->rxreq_gntref;
SLIST_REMOVE_HEAD(&sc->sc_rxreq_head, rxreq_next);
sc->sc_free_rxreql--;
}
if (i > 0) {
sc->sc_rx_ring.req_prod_pvt = req_prod + i;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&sc->sc_rx_ring, notify);
if (notify)
hypervisor_notify_via_evtchn(sc->sc_evtchn);
}
}
static void
xennet_free_rx_buffer(struct xennet_xenbus_softc *sc, bool revoke)
{
RING_IDX i;
KASSERT(mutex_owned(&sc->sc_rx_lock));
DPRINTF(("%s: xennet_free_rx_buffer\n", device_xname(sc->sc_dev)));
for (i = 0; i < NET_RX_RING_SIZE; i++) {
struct xennet_rxreq *rxreq = &sc->sc_rxreqs[i];
if (rxreq->rxreq_gntref != GRANT_INVALID_REF) {
SLIST_INSERT_HEAD(&sc->sc_rxreq_head, rxreq,
rxreq_next);
sc->sc_free_rxreql++;
if (revoke)
xengnt_revoke_access(rxreq->rxreq_gntref);
rxreq->rxreq_gntref = GRANT_INVALID_REF;
}
if (rxreq->rxreq_m != NULL) {
m_freem(rxreq->rxreq_m);
rxreq->rxreq_m = NULL;
}
}
DPRINTF(("%s: xennet_free_rx_buffer done\n", device_xname(sc->sc_dev)));
}
static void
xennet_rx_mbuf_free(struct mbuf *m, void *buf, size_t size, void *arg)
{
KASSERT(buf == m->m_ext.ext_buf);
KASSERT(arg == NULL);
KASSERT(m != NULL);
vaddr_t va = (vaddr_t)(buf) & ~((vaddr_t)PAGE_MASK);
pool_cache_put_paddr(if_xennetrxbuf_cache,
(void *)va, m->m_ext.ext_paddr);
pool_cache_put(mb_cache, m);
};
static void
xennet_rx_free_req(struct xennet_xenbus_softc *sc, struct xennet_rxreq *req)
{
KASSERT(mutex_owned(&sc->sc_rx_lock));
SLIST_INSERT_HEAD(&sc->sc_rxreq_head, req, rxreq_next);
sc->sc_free_rxreql++;
if (sc->sc_free_rxreql >= (NET_RX_RING_SIZE * 4 / 5) &&
__predict_true(sc->sc_backend_status == BEST_CONNECTED)) {
xennet_alloc_rx_buffer(sc);
}
}
static void
xennet_tx_complete(struct xennet_xenbus_softc *sc)
{
struct xennet_txreq *req;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
RING_IDX resp_prod, i;
DPRINTFN(XEDB_EVENT, ("xennet_tx_complete prod %d cons %d\n",
sc->sc_tx_ring.sring->rsp_prod, sc->sc_tx_ring.rsp_cons));
KASSERT(mutex_owned(&sc->sc_tx_lock));
again:
resp_prod = sc->sc_tx_ring.sring->rsp_prod;
xen_rmb();
for (i = sc->sc_tx_ring.rsp_cons; i != resp_prod; i++) {
req = &sc->sc_txreqs[RING_GET_RESPONSE(&sc->sc_tx_ring, i)->id];
KASSERT(req->txreq_id ==
RING_GET_RESPONSE(&sc->sc_tx_ring, i)->id);
KASSERT(xengnt_status(req->txreq_gntref) == 0);
xengnt_revoke_access(req->txreq_gntref);
req->txreq_gntref = GRANT_INVALID_REF;
if (req->txreq_m) {
if (__predict_false(
RING_GET_RESPONSE(&sc->sc_tx_ring, i)->status !=
NETIF_RSP_OKAY))
if_statinc(ifp, if_oerrors);
else
if_statinc(ifp, if_opackets);
bus_dmamap_unload(sc->sc_xbusd->xbusd_dmat,
req->txreq_dmamap);
m_freem(req->txreq_m);
req->txreq_m = NULL;
}
SLIST_INSERT_HEAD(&sc->sc_txreq_head, req, txreq_next);
sc->sc_free_txreql++;
}
sc->sc_tx_ring.rsp_cons = resp_prod;
xen_wmb();
sc->sc_tx_ring.sring->rsp_event =
resp_prod + ((sc->sc_tx_ring.sring->req_prod - resp_prod) >> 1) + 1;
xen_mb();
if (resp_prod != sc->sc_tx_ring.sring->rsp_prod)
goto again;
}
static int
xennet_handler(void *arg)
{
struct xennet_xenbus_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
RING_IDX resp_prod, i;
struct xennet_rxreq *req;
struct mbuf *m, *m0;
int rxflags, m0_rxflags;
int more_to_do;
if (sc->sc_backend_status != BEST_CONNECTED)
return 1;
if_schedule_deferred_start(ifp);
rnd_add_uint32(&sc->sc_rnd_source, sc->sc_tx_ring.req_prod_pvt);
again:
DPRINTFN(XEDB_EVENT, ("xennet_handler prod %d cons %d\n",
sc->sc_rx_ring.sring->rsp_prod, sc->sc_rx_ring.rsp_cons));
mutex_enter(&sc->sc_rx_lock);
resp_prod = sc->sc_rx_ring.sring->rsp_prod;
xen_rmb();
m0 = NULL;
for (i = sc->sc_rx_ring.rsp_cons; i != resp_prod; i++) {
netif_rx_response_t *rx = RING_GET_RESPONSE(&sc->sc_rx_ring, i);
req = &sc->sc_rxreqs[rx->id];
KASSERT(req->rxreq_gntref != GRANT_INVALID_REF);
KASSERT(req->rxreq_id == rx->id);
xengnt_revoke_access(req->rxreq_gntref);
req->rxreq_gntref = GRANT_INVALID_REF;
m = req->rxreq_m;
req->rxreq_m = NULL;
m->m_len = m->m_pkthdr.len = rx->status;
bus_dmamap_sync(sc->sc_xbusd->xbusd_dmat, req->rxreq_dmamap, 0,
m->m_pkthdr.len, BUS_DMASYNC_PREREAD);
if (m0 == NULL) {
MCLAIM(m, &sc->sc_ethercom.ec_rx_mowner);
m_set_rcvif(m, ifp);
}
rxflags = rx->flags;
if (m0 || rxflags & NETRXF_more_data) {
if (m0 == NULL) {
m0 = m;
m0_rxflags = rxflags;
} else {
m_cat(m0, m);
m0->m_pkthdr.len += m->m_len;
}
if (rxflags & NETRXF_more_data) {
xennet_rx_free_req(sc, req);
continue;
}
sc->sc_cnt_rx_frag.ev_count++;
m = m0;
m0 = NULL;
rxflags = m0_rxflags;
}
if (rxflags & NETRXF_csum_blank) {
xennet_checksum_fill(ifp, m, &sc->sc_cnt_rx_cksum_blank,
&sc->sc_cnt_rx_cksum_undefer);
} else if (rxflags & NETRXF_data_validated)
m->m_pkthdr.csum_flags = XN_M_CSUM_SUPPORTED;
xennet_rx_free_req(sc, req);
if_percpuq_enqueue(ifp->if_percpuq, m);
}
if (m0) {
if_statinc(ifp, if_iqdrops);
m_freem(m0);
}
sc->sc_rx_ring.rsp_cons = i;
xen_wmb();
RING_FINAL_CHECK_FOR_RESPONSES(&sc->sc_rx_ring, more_to_do);
mutex_exit(&sc->sc_rx_lock);
if (more_to_do) {
DPRINTF(("%s: %s more_to_do\n", ifp->if_xname, __func__));
goto again;
}
return 1;
}
static bool
xennet_submit_tx_request(struct xennet_xenbus_softc *sc, struct mbuf *m,
struct xennet_txreq *req0, int *req_prod)
{
struct xennet_txreq *req = req0;
netif_tx_request_t *txreq;
int i, prod = *req_prod;
const bool multiseg = (req0->txreq_dmamap->dm_nsegs > 1);
const int lastseg = req0->txreq_dmamap->dm_nsegs - 1;
bus_dma_segment_t *ds;
SLIST_HEAD(, xennet_txreq) txchain;
KASSERT(mutex_owned(&sc->sc_tx_lock));
KASSERT(req0->txreq_dmamap->dm_nsegs > 0);
bus_dmamap_sync(sc->sc_xbusd->xbusd_dmat, req->txreq_dmamap, 0,
m->m_pkthdr.len, BUS_DMASYNC_POSTWRITE);
MCLAIM(m, &sc->sc_ethercom.ec_tx_mowner);
SLIST_INIT(&txchain);
for (i = 0; i < req0->txreq_dmamap->dm_nsegs; i++) {
KASSERT(req != NULL);
ds = &req0->txreq_dmamap->dm_segs[i];
if (__predict_false(xengnt_grant_access(
sc->sc_xbusd->xbusd_otherend_id,
trunc_page(ds->ds_addr),
GNTMAP_readonly, &req->txreq_gntref) != 0)) {
goto grant_fail;
}
KASSERT(SLIST_FIRST(&sc->sc_txreq_head) == req);
SLIST_REMOVE_HEAD(&sc->sc_txreq_head, txreq_next);
SLIST_INSERT_HEAD(&txchain, req, txreq_next);
sc->sc_free_txreql--;
req->txreq_m = (req == req0) ? m : NULL;
txreq = RING_GET_REQUEST(&sc->sc_tx_ring, prod + i);
txreq->id = req->txreq_id;
txreq->gref = req->txreq_gntref;
txreq->offset = ds->ds_addr & PAGE_MASK;
txreq->size = (i == 0) ? m->m_pkthdr.len : ds->ds_len;
txreq->flags = 0;
if (i == 0) {
if (m->m_pkthdr.csum_flags & XN_M_CSUM_SUPPORTED) {
txreq->flags |= NETTXF_csum_blank;
} else {
#if 0
txreq->flags |= NETTXF_data_validated;
#endif
}
}
if (multiseg && i < lastseg)
txreq->flags |= NETTXF_more_data;
req = SLIST_FIRST(&sc->sc_txreq_head);
}
if (i > 1)
sc->sc_cnt_tx_frag.ev_count++;
*req_prod += i;
return true;
grant_fail:
printf("%s: grant_access failed\n", device_xname(sc->sc_dev));
while (!SLIST_EMPTY(&txchain)) {
req = SLIST_FIRST(&txchain);
SLIST_REMOVE_HEAD(&txchain, txreq_next);
xengnt_revoke_access(req->txreq_gntref);
req->txreq_gntref = GRANT_INVALID_REF;
SLIST_INSERT_HEAD(&sc->sc_txreq_head, req, txreq_next);
sc->sc_free_txreql++;
}
req0->txreq_m = NULL;
return false;
}
void
xennet_start(struct ifnet *ifp)
{
struct xennet_xenbus_softc *sc = ifp->if_softc;
struct mbuf *m;
RING_IDX req_prod;
struct xennet_txreq *req;
int notify;
mutex_enter(&sc->sc_tx_lock);
rnd_add_uint32(&sc->sc_rnd_source, sc->sc_tx_ring.req_prod_pvt);
xennet_tx_complete(sc);
req_prod = sc->sc_tx_ring.req_prod_pvt;
while (1) {
req = SLIST_FIRST(&sc->sc_txreq_head);
if (__predict_false(req == NULL)) {
if (!IFQ_IS_EMPTY(&ifp->if_snd))
sc->sc_cnt_tx_queue_full.ev_count++;
break;
}
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == NULL)
break;
if (m->m_pkthdr.len < MCLBYTES && m->m_next)
(void)m_defrag(m, M_DONTWAIT);
if (__predict_false(bus_dmamap_load_mbuf(
sc->sc_xbusd->xbusd_dmat,
req->txreq_dmamap, m, BUS_DMA_NOWAIT) != 0)) {
sc->sc_cnt_tx_defrag.ev_count++;
if (__predict_false(m_defrag(m, M_DONTWAIT) == NULL)) {
DPRINTF(("%s: defrag failed\n",
device_xname(sc->sc_dev)));
m_freem(m);
break;
}
if (__predict_false(bus_dmamap_load_mbuf(
sc->sc_xbusd->xbusd_dmat,
req->txreq_dmamap, m, BUS_DMA_NOWAIT) != 0)) {
printf("%s: cannot load new mbuf len %d\n",
device_xname(sc->sc_dev),
m->m_pkthdr.len);
m_freem(m);
break;
}
}
if (req->txreq_dmamap->dm_nsegs > sc->sc_free_txreql) {
sc->sc_cnt_tx_queue_full.ev_count++;
sc->sc_cnt_tx_drop.ev_count++;
bus_dmamap_unload(sc->sc_xbusd->xbusd_dmat,
req->txreq_dmamap);
m_freem(m);
break;
}
DPRINTFN(XEDB_MBUF, ("xennet_start id %d, "
"mbuf %p, buf %p, size %d\n",
req->txreq_id, m, mtod(m, void *), m->m_pkthdr.len));
#ifdef XENNET_DEBUG_DUMP
xennet_hex_dump(mtod(m, u_char *), m->m_pkthdr.len, "s",
req->txreq_id);
#endif
if (!xennet_submit_tx_request(sc, m, req, &req_prod)) {
sc->sc_cnt_tx_drop.ev_count++;
bus_dmamap_unload(sc->sc_xbusd->xbusd_dmat,
req->txreq_dmamap);
m_freem(m);
break;
}
bpf_mtap(ifp, m, BPF_D_OUT);
}
sc->sc_tx_ring.req_prod_pvt = req_prod;
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&sc->sc_tx_ring, notify);
if (notify)
hypervisor_notify_via_evtchn(sc->sc_evtchn);
mutex_exit(&sc->sc_tx_lock);
DPRINTFN(XEDB_FOLLOW, ("%s: xennet_start() done\n",
device_xname(sc->sc_dev)));
}
int
xennet_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
#ifdef XENNET_DEBUG
struct xennet_xenbus_softc *sc = ifp->if_softc;
#endif
int error = 0;
#ifdef NET_MPSAFE
#ifdef notyet
KASSERT(IFNET_LOCKED(ifp));
#endif
#endif
int s = splnet();
DPRINTFN(XEDB_FOLLOW, ("%s: xennet_ioctl()\n",
device_xname(sc->sc_dev)));
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET)
error = 0;
DPRINTFN(XEDB_FOLLOW, ("%s: xennet_ioctl() returning %d\n",
device_xname(sc->sc_dev), error));
splx(s);
return error;
}
int
xennet_init(struct ifnet *ifp)
{
struct xennet_xenbus_softc *sc = ifp->if_softc;
KASSERT(IFNET_LOCKED(ifp));
DPRINTFN(XEDB_FOLLOW, ("%s: xennet_init()\n",
device_xname(sc->sc_dev)));
if ((ifp->if_flags & IFF_RUNNING) == 0) {
mutex_enter(&sc->sc_rx_lock);
sc->sc_rx_ring.sring->rsp_event =
sc->sc_rx_ring.rsp_cons + 1;
mutex_exit(&sc->sc_rx_lock);
hypervisor_unmask_event(sc->sc_evtchn);
hypervisor_notify_via_evtchn(sc->sc_evtchn);
}
ifp->if_flags |= IFF_RUNNING;
return 0;
}
void
xennet_stop(struct ifnet *ifp, int disable)
{
struct xennet_xenbus_softc *sc = ifp->if_softc;
KASSERT(IFNET_LOCKED(ifp));
ifp->if_flags &= ~IFF_RUNNING;
hypervisor_mask_event(sc->sc_evtchn);
}
static void
xennet_sysctl_init(struct xennet_xenbus_softc *sc)
{
int rc, xennet_root_num;
const struct sysctlnode *node;
if ((rc = sysctl_createv(&xennet_log, 0, NULL, &node,
0, CTLTYPE_NODE, "xennet",
SYSCTL_DESCR("XENNET interface controls"),
NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) {
goto out;
}
xennet_root_num = node->sysctl_num;
if ((rc = sysctl_createv(&xennet_log, 0, NULL, &node,
CTLFLAG_READWRITE,
CTLTYPE_INT, "xnfrx_lowat",
SYSCTL_DESCR("xnfrx low water threshold"),
xennet_sysctl_verify, 0,
&if_xnfrx_lowat,
0, CTL_HW, xennet_root_num, CTL_CREATE,
CTL_EOL)) != 0) {
goto out;
}
xennet_xnfrx_nodenum = node->sysctl_num;
return;
out:
aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
}
static int
xennet_sysctl_verify(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int*)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
#if 0
DPRINTF2(("%s: t = %d, nodenum = %d, rnodenum = %d\n", __func__, t,
node.sysctl_num, rnode->sysctl_num));
#endif
if (node.sysctl_num == xennet_xnfrx_nodenum) {
if (t < 0 || t >= IF_XNFRX_LOWAT_MAX)
return EINVAL;
if_xnfrx_lowat = t;
pool_cache_setlowat(if_xennetrxbuf_cache, if_xnfrx_lowat);
} else
return EINVAL;
*(int*)rnode->sysctl_data = t;
return 0;
}
#if defined(NFS_BOOT_BOOTSTATIC)
int
xennet_bootstatic_callback(struct nfs_diskless *nd)
{
#if 0
struct ifnet *ifp = nd->nd_ifp;
struct xennet_xenbus_softc *sc =
(struct xennet_xenbus_softc *)ifp->if_softc;
#endif
int flags = 0;
union xen_cmdline_parseinfo xcp;
struct sockaddr_in *sin;
memset(&xcp, 0, sizeof(xcp.xcp_netinfo));
xcp.xcp_netinfo.xi_ifno = 0;
xcp.xcp_netinfo.xi_root = nd->nd_root.ndm_host;
xen_parse_cmdline(XEN_PARSE_NETINFO, &xcp);
if (xcp.xcp_netinfo.xi_root[0] != '\0') {
flags |= NFS_BOOT_HAS_SERVER;
if (strchr(xcp.xcp_netinfo.xi_root, ':') != NULL)
flags |= NFS_BOOT_HAS_ROOTPATH;
}
nd->nd_myip.s_addr = ntohl(xcp.xcp_netinfo.xi_ip[0]);
nd->nd_gwip.s_addr = ntohl(xcp.xcp_netinfo.xi_ip[2]);
nd->nd_mask.s_addr = ntohl(xcp.xcp_netinfo.xi_ip[3]);
sin = (struct sockaddr_in *) &nd->nd_root.ndm_saddr;
memset((void *)sin, 0, sizeof(*sin));
sin->sin_len = sizeof(*sin);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = ntohl(xcp.xcp_netinfo.xi_ip[1]);
if (nd->nd_myip.s_addr)
flags |= NFS_BOOT_HAS_MYIP;
if (nd->nd_gwip.s_addr)
flags |= NFS_BOOT_HAS_GWIP;
if (nd->nd_mask.s_addr)
flags |= NFS_BOOT_HAS_MASK;
if (sin->sin_addr.s_addr)
flags |= NFS_BOOT_HAS_SERVADDR;
return flags;
}
#endif
#ifdef XENNET_DEBUG_DUMP
#define XCHR(x) hexdigits[(x) & 0xf]
static void
xennet_hex_dump(const unsigned char *pkt, size_t len, const char *type, int id)
{
size_t i, j;
printf("pkt %p len %zd/%zx type %s id %d\n", pkt, len, len, type, id);
printf("00000000 ");
for(i=0; i<len; i++) {
printf("%c%c ", XCHR(pkt[i]>>4), XCHR(pkt[i]));
if ((i+1) % 16 == 8)
printf(" ");
if ((i+1) % 16 == 0) {
printf(" %c", '|');
for(j=0; j<16; j++)
printf("%c", pkt[i-15+j]>=32 &&
pkt[i-15+j]<127?pkt[i-15+j]:'.');
printf("%c\n%c%c%c%c%c%c%c%c ", '|',
XCHR((i+1)>>28), XCHR((i+1)>>24),
XCHR((i+1)>>20), XCHR((i+1)>>16),
XCHR((i+1)>>12), XCHR((i+1)>>8),
XCHR((i+1)>>4), XCHR(i+1));
}
}
printf("\n");
}
#undef XCHR
#endif