if_shmem.c revision 1.13 1 /* $NetBSD: if_shmem.c,v 1.13 2010/08/10 18:17:12 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2009 Antti Kantee. All Rights Reserved.
5 *
6 * Development of this software was supported by The Nokia Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #include <sys/cdefs.h>
31 __KERNEL_RCSID(0, "$NetBSD: if_shmem.c,v 1.13 2010/08/10 18:17:12 pooka Exp $");
32
33 #include <sys/param.h>
34 #include <sys/atomic.h>
35 #include <sys/fcntl.h>
36 #include <sys/kmem.h>
37 #include <sys/kthread.h>
38 #include <sys/lock.h>
39 #include <sys/atomic.h>
40
41 #include <net/if.h>
42 #include <net/if_ether.h>
43
44 #include <netinet/in.h>
45 #include <netinet/in_var.h>
46
47 #include <rump/rump.h>
48 #include <rump/rumpuser.h>
49
50 #include "rump_private.h"
51 #include "rump_net_private.h"
52
53 #if 0
54 #define DPRINTF(x) rumpuser_dprintf x
55 #else
56 #define DPRINTF(x)
57 #endif
58
59 /*
60 * A virtual ethernet interface which uses shared memory from a
61 * memory mapped file as the bus.
62 */
63
64 static int shmif_init(struct ifnet *);
65 static int shmif_ioctl(struct ifnet *, u_long, void *);
66 static void shmif_start(struct ifnet *);
67 static void shmif_stop(struct ifnet *, int);
68
69 struct shmif_sc {
70 struct ethercom sc_ec;
71 uint8_t sc_myaddr[6];
72 uint8_t *sc_busmem;
73 int sc_memfd;
74 int sc_kq;
75
76 uint32_t sc_nextpacket;
77 uint32_t sc_prevgen;
78 };
79 #define IFMEM_LOCK (0)
80 #define IFMEM_GENERATION (8)
81 #define IFMEM_LASTPACKET (12)
82 #define IFMEM_WAKEUP (16)
83 #define IFMEM_DATA (20)
84
85 #define BUSCTRL_ATOFF(sc, off) ((uint32_t *)(sc->sc_busmem+(off)))
86
87 #define BUSMEM_SIZE (1024*1024) /* need write throttling? */
88
89 static void shmif_rcv(void *);
90
91 static uint32_t numif;
92
93 #define LOCK_UNLOCKED 0
94 #define LOCK_LOCKED 1
95
96 /*
97 * This locking needs work and will misbehave severely if:
98 * 1) the backing memory has to be paged in
99 * 2) some lockholder exits while holding the lock
100 */
101 static void
102 lockbus(struct shmif_sc *sc)
103 {
104
105 while (atomic_cas_uint((void *)sc->sc_busmem,
106 LOCK_UNLOCKED, LOCK_LOCKED) == LOCK_LOCKED)
107 continue;
108 membar_enter();
109 }
110
111 static void
112 unlockbus(struct shmif_sc *sc)
113 {
114 unsigned int old;
115
116 membar_exit();
117 old = atomic_swap_uint((void *)sc->sc_busmem, LOCK_UNLOCKED);
118 KASSERT(old == LOCK_LOCKED);
119 }
120
121 static uint32_t
122 busread(struct shmif_sc *sc, void *dest, uint32_t off, size_t len)
123 {
124 size_t chunk;
125
126 KASSERT(len < (BUSMEM_SIZE - IFMEM_DATA) && off <= BUSMEM_SIZE);
127 chunk = MIN(len, BUSMEM_SIZE - off);
128 memcpy(dest, sc->sc_busmem + off, chunk);
129 len -= chunk;
130
131 if (len == 0)
132 return off + chunk;
133
134 /* else, wraps around */
135 off = IFMEM_DATA;
136 sc->sc_prevgen = *BUSCTRL_ATOFF(sc, IFMEM_GENERATION);
137
138 /* finish reading */
139 memcpy((uint8_t *)dest + chunk, sc->sc_busmem + off, len);
140 return off + len;
141 }
142
143 static uint32_t
144 buswrite(struct shmif_sc *sc, uint32_t off, void *data, size_t len)
145 {
146 size_t chunk;
147
148 KASSERT(len < (BUSMEM_SIZE - IFMEM_DATA) && off <= BUSMEM_SIZE
149 && off >= IFMEM_DATA);
150
151 chunk = MIN(len, BUSMEM_SIZE - off);
152 memcpy(sc->sc_busmem + off, data, chunk);
153 len -= chunk;
154
155 if (len == 0)
156 return off + chunk;
157
158 DPRINTF(("buswrite wrap: wrote %d bytes to %d, left %d to %d",
159 chunk, off, len, IFMEM_DATA));
160
161 /* else, wraps around */
162 off = IFMEM_DATA;
163 (*BUSCTRL_ATOFF(sc, IFMEM_GENERATION))++;
164 sc->sc_prevgen = *BUSCTRL_ATOFF(sc, IFMEM_GENERATION);
165
166 /* finish writing */
167 memcpy(sc->sc_busmem + off, (uint8_t *)data + chunk, len);
168 return off + len;
169 }
170
171 static inline uint32_t
172 advance(uint32_t oldoff, uint32_t delta)
173 {
174 uint32_t newoff;
175
176 newoff = oldoff + delta;
177 if (newoff >= BUSMEM_SIZE)
178 newoff -= (BUSMEM_SIZE - IFMEM_DATA);
179 return newoff;
180
181 }
182
183 static uint32_t
184 nextpktoff(struct shmif_sc *sc, uint32_t oldoff)
185 {
186 uint32_t oldlen;
187
188 busread(sc, &oldlen, oldoff, 4);
189 KASSERT(oldlen < BUSMEM_SIZE - IFMEM_DATA);
190
191 return advance(oldoff, 4 + oldlen);
192 }
193
194 int
195 rump_shmif_create(const char *path, int *ifnum)
196 {
197 struct shmif_sc *sc;
198 struct ifnet *ifp;
199 uint8_t enaddr[ETHER_ADDR_LEN] = { 0xb2, 0xa0, 0x00, 0x00, 0x00, 0x00 };
200 uint32_t randnum;
201 unsigned mynum;
202 int error;
203
204 randnum = arc4random();
205 memcpy(&enaddr[2], &randnum, 4);
206 mynum = atomic_inc_uint_nv(&numif)-1;
207
208 sc = kmem_zalloc(sizeof(*sc), KM_SLEEP);
209 ifp = &sc->sc_ec.ec_if;
210 memcpy(sc->sc_myaddr, enaddr, sizeof(enaddr));
211
212 sc->sc_memfd = rumpuser_open(path, O_RDWR | O_CREAT, &error);
213 if (sc->sc_memfd == -1)
214 goto fail;
215 sc->sc_busmem = rumpuser_filemmap(sc->sc_memfd, 0, BUSMEM_SIZE,
216 RUMPUSER_FILEMMAP_TRUNCATE | RUMPUSER_FILEMMAP_SHARED
217 | RUMPUSER_FILEMMAP_READ | RUMPUSER_FILEMMAP_WRITE, &error);
218 if (error)
219 goto fail;
220
221 lockbus(sc);
222 if (*BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET) == 0)
223 *BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET) = IFMEM_DATA;
224 sc->sc_nextpacket = *BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET);
225 sc->sc_prevgen = *BUSCTRL_ATOFF(sc, IFMEM_GENERATION);
226 unlockbus(sc);
227
228 sc->sc_kq = rumpuser_writewatchfile_setup(-1, sc->sc_memfd, 0, &error);
229 if (sc->sc_kq == -1)
230 goto fail;
231
232 sprintf(ifp->if_xname, "shmif%d", mynum);
233 ifp->if_softc = sc;
234 ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
235 ifp->if_init = shmif_init;
236 ifp->if_ioctl = shmif_ioctl;
237 ifp->if_start = shmif_start;
238 ifp->if_stop = shmif_stop;
239 ifp->if_mtu = 1518;
240
241 if_attach(ifp);
242 ether_ifattach(ifp, enaddr);
243
244 aprint_verbose("shmif%d: bus %s\n", mynum, path);
245 aprint_verbose("shmif%d: Ethernet address %s\n",
246 mynum, ether_sprintf(enaddr));
247
248 if (ifnum)
249 *ifnum = mynum;
250 return 0;
251
252 fail:
253 panic("rump_shmemif_create: fixme");
254 }
255
256 static int
257 shmif_init(struct ifnet *ifp)
258 {
259 int error = 0;
260
261 if (rump_threads) {
262 error = kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
263 shmif_rcv, ifp, NULL, "shmif");
264 } else {
265 printf("WARNING: threads not enabled, shmif NOT working\n");
266 }
267
268 ifp->if_flags |= IFF_RUNNING;
269 return error;
270 }
271
272 static int
273 shmif_ioctl(struct ifnet *ifp, u_long cmd, void *data)
274 {
275 int s, rv;
276
277 s = splnet();
278 rv = ether_ioctl(ifp, cmd, data);
279 if (rv == ENETRESET)
280 rv = 0;
281 splx(s);
282
283 return rv;
284 }
285
286 /* send everything in-context */
287 static void
288 shmif_start(struct ifnet *ifp)
289 {
290 struct shmif_sc *sc = ifp->if_softc;
291 struct mbuf *m, *m0;
292 uint32_t lastoff, dataoff, npktlenoff;
293 uint32_t pktsize = 0;
294 bool wrote = false;
295 int error;
296
297 for (;;) {
298 IF_DEQUEUE(&ifp->if_snd, m0);
299 if (m0 == NULL) {
300 break;
301 }
302
303 lockbus(sc);
304 lastoff = *BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET);
305
306 npktlenoff = nextpktoff(sc, lastoff);
307 dataoff = advance(npktlenoff, 4);
308
309 for (m = m0; m != NULL; m = m->m_next) {
310 pktsize += m->m_len;
311 dataoff = buswrite(sc, dataoff, mtod(m, void *),
312 m->m_len);
313 }
314 buswrite(sc, npktlenoff, &pktsize, 4);
315 *BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET) = npktlenoff;
316 unlockbus(sc);
317
318 m_freem(m0);
319 wrote = true;
320
321 DPRINTF(("shmif_start: send %d bytes at off %d\n",
322 pktsize, npktlenoff));
323 }
324 /* wakeup */
325 if (wrote)
326 rumpuser_pwrite(sc->sc_memfd, &error, 4, IFMEM_WAKEUP, &error);
327 }
328
329 static void
330 shmif_stop(struct ifnet *ifp, int disable)
331 {
332
333 panic("%s: unimpl", __func__);
334 }
335
336 static void
337 shmif_rcv(void *arg)
338 {
339 struct ifnet *ifp = arg;
340 struct shmif_sc *sc = ifp->if_softc;
341 struct mbuf *m = NULL;
342 struct ether_header *eth;
343 uint32_t nextpkt, pktlen, lastpkt, busgen, lastnext;
344 int error;
345
346 for (;;) {
347 if (m == NULL) {
348 m = m_gethdr(M_WAIT, MT_DATA);
349 MCLGET(m, M_WAIT);
350 }
351
352 DPRINTF(("waiting %d/%d\n", sc->sc_nextpacket, sc->sc_prevgen));
353
354 KASSERT(m->m_flags & M_EXT);
355 lockbus(sc);
356 lastpkt = *BUSCTRL_ATOFF(sc, IFMEM_LASTPACKET);
357 busgen = *BUSCTRL_ATOFF(sc, IFMEM_GENERATION);
358 lastnext = nextpktoff(sc, lastpkt);
359 if ((lastnext > sc->sc_nextpacket && busgen > sc->sc_prevgen)
360 || (busgen > sc->sc_prevgen+1)) {
361 nextpkt = lastpkt;
362 sc->sc_prevgen = busgen;
363 rumpuser_dprintf("shmif_rcv: generation overrun, "
364 "skipping invalid packets\n");
365 } else {
366 nextpkt = sc->sc_nextpacket;
367 }
368
369 /* need more data? */
370 if (lastnext == nextpkt && sc->sc_prevgen == busgen){
371 unlockbus(sc);
372 error = 0;
373 rumpuser_writewatchfile_wait(sc->sc_kq, NULL, &error);
374 if (__predict_false(error))
375 printf("shmif_rcv: wait failed %d\n", error);
376 continue;
377 }
378
379 busread(sc, &pktlen, nextpkt, 4);
380 busread(sc, mtod(m, void *), advance(nextpkt, 4), pktlen);
381
382 DPRINTF(("shmif_rcv: read packet of length %d at %d\n",
383 pktlen, nextpkt));
384
385 sc->sc_nextpacket = nextpktoff(sc, nextpkt);
386 sc->sc_prevgen = busgen;
387 unlockbus(sc);
388
389 m->m_len = m->m_pkthdr.len = pktlen;
390 m->m_pkthdr.rcvif = ifp;
391
392 /* if it's from us, don't pass up and reuse storage space */
393 eth = mtod(m, struct ether_header *);
394 if (memcmp(eth->ether_shost, sc->sc_myaddr, 6) != 0) {
395 ifp->if_input(ifp, m);
396 m = NULL;
397 }
398 }
399
400 panic("shmif_worker is a lazy boy %d\n", error);
401 }
402