// SPDX-License-Identifier: Apache-2.0 // // Socket buffers. See ldls/kpi/kpi.h for the provenance rule. // // An `sk_buff` is the unit a Linux driver receives into and transmits from, so // a host that cannot make one cannot carry a packet. It is also the structure // with the most fields a driver touches directly, which is why every offset // here comes from BTF rather than from anything written down. // // The layout is worth one note because it is not what the names suggest: // `head` and `data` are pointers, but `tail` and `end` are **32-bit offsets // from head**, not pointers. Treating them as pointers produces something that // works for small packets on a lucky allocation and then does not. #include "types.h" #include "user.h" #include "posix.h" #include "kpi.h" // Room in front of the data for headers a stack might push. Linux reserves // this so that adding an Ethernet header does not mean copying the payload; // nothing here pushes anything, but a driver may rely on `skb_reserve` having // somewhere to go. #define KPI_HEADROOM 64 static unsigned int rd32(void *base, unsigned int off) { unsigned int v; unsigned char *p = (unsigned char *)base + off; int i; for (i = 0; i < 4; i++) ((unsigned char *)&v)[i] = p[i]; return v; } static void wr32(void *base, unsigned int off, unsigned int v) { unsigned char *p = (unsigned char *)base + off; int i; for (i = 0; i < 4; i++) p[i] = ((unsigned char *)&v)[i]; } static void * rdptr(void *base, unsigned int off) { void *v; unsigned char *p = (unsigned char *)base + off; int i; for (i = 0; i < 8; i++) ((unsigned char *)&v)[i] = p[i]; return v; } static void wrptr(void *base, unsigned int off, void *v) { unsigned char *p = (unsigned char *)base + off; int i; for (i = 0; i < 8; i++) p[i] = ((unsigned char *)&v)[i]; } // One buffer: the control block, then headroom, then the frame. // // Allocated in one piece so that freeing is a no-op like every other // allocation here -- see the note in kpi.c about a hosted driver's lifetime. void * kpi_skb_alloc(unsigned int len) { unsigned char *p; unsigned int total = kpi_l.skb_size + KPI_HEADROOM + len + 64; unsigned char *buf; if (kpi_l.skb_size == 0) return 0; p = mmap(0, total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == 0 || (long)p == -1) return 0; buf = p + kpi_l.skb_size; wrptr(p, kpi_l.skb_head, buf); wrptr(p, kpi_l.skb_data, buf + KPI_HEADROOM); wr32(p, kpi_l.skb_tail, KPI_HEADROOM); wr32(p, kpi_l.skb_end, KPI_HEADROOM + len + 64); wr32(p, kpi_l.skb_len, 0); return p; } void *kpi_napi_alloc_skb(void *napi, unsigned int len) { (void)napi; return kpi_skb_alloc(len); } void *kpi_netdev_alloc_skb(void *dev, unsigned int len) { (void)dev; return kpi_skb_alloc(len); } // Claim `n` bytes at the tail and return where they start. The classic, and // the one every receive path is built on. void * kpi_skb_put(void *skb, unsigned int n) { unsigned char *head = (unsigned char *)rdptr(skb, kpi_l.skb_head); unsigned int tail = rd32(skb, kpi_l.skb_tail); wr32(skb, kpi_l.skb_tail, tail + n); wr32(skb, kpi_l.skb_len, rd32(skb, kpi_l.skb_len) + n); return head + tail; } void * kpi_skb_reserve(void *skb, int n) { unsigned char *d = (unsigned char *)rdptr(skb, kpi_l.skb_data); wrptr(skb, kpi_l.skb_data, d + n); wr32(skb, kpi_l.skb_tail, rd32(skb, kpi_l.skb_tail) + (unsigned int)n); return 0; } unsigned char *kpi_skb_data(void *skb) { return rdptr(skb, kpi_l.skb_data); } unsigned int kpi_skb_len(void *skb) { return rd32(skb, kpi_l.skb_len); } // Copy a frame out of an skb, which is what a driver does on transmit when it // cannot hand the buffer to the hardware directly. The checksum half of the // name does not apply: nothing here offloads one. void kpi_skb_copy_and_csum_dev(void *skb, unsigned char *to) { unsigned char *d = rdptr(skb, kpi_l.skb_data); unsigned int n = rd32(skb, kpi_l.skb_len), i; for (i = 0; i < n; i++) to[i] = d[i]; } // The driver has a frame. Hand it to the stack. // // "The stack" is the kernel on the other side of /dev/netdev, and this is the // point at which a packet that arrived on real hardware becomes an ordinary // frame on an ordinary interface. Everything before it is the driver's; nothing // after it knows a Linux driver was involved. int kpi_netif_receive_skb(void *skb) { unsigned char *d = rdptr(skb, kpi_l.skb_data); unsigned int n = rd32(skb, kpi_l.skb_len); if (kpi.netfd >= 0 && n > 0 && n <= 1536) write(kpi.netfd, (char *)d, (int)n); return 0; } // eth_type_trans(skb, dev) -- consume the Ethernet header and report the // protocol. The stack below wants the whole frame including that header, so // unlike Linux's this does not pull it off; what it returns is the type field, // which the driver passes on and nothing here reads. unsigned short kpi_eth_type_trans(void *skb, void *dev) { unsigned char *d = rdptr(skb, kpi_l.skb_data); unsigned int n = rd32(skb, kpi_l.skb_len); wrptr(skb, kpi_l.skb_dev, dev); if (n < 14) return 0; return (unsigned short)((d[12] << 8) | d[13]); } void kpi_kfree_skb(void *skb) { (void)skb; } void kpi_kfree_skb_reason(void *skb, int r) { (void)skb; (void)r; }