/* $NetBSD: if_shmem.c,v 1.89 2024/10/01 08:55:58 rin Exp $ */
/*
* Copyright (c) 2009, 2010 Antti Kantee. All Rights Reserved.
*
* Development of this software was supported by The Nokia Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Do r/w prefault for backend pages when attaching the interface.
* At least logically thinking improves performance (although no
* mlocking is done, so they might go away).
*/
#define PREFAULT_RW
/*
* A virtual ethernet interface which uses shared memory from a
* memory mapped file as the bus.
*/
/*
* This locking needs work and will misbehave severely if:
* 1) the backing memory has to be paged in
* 2) some lockholder exits while holding the lock
*/
static void
shmif_lockbus(struct shmif_mem *busmem)
{
int i = 0;
while (__predict_false(atomic_cas_32(&busmem->shm_lock,
LOCK_UNLOCKED, LOCK_LOCKED) == LOCK_LOCKED)) {
if (__predict_false(++i > LOCK_COOLDOWN)) {
/* wait 1ms */
rumpuser_clock_sleep(RUMPUSER_CLOCK_RELWALL,
0, 1000*1000);
i = 0;
}
continue;
}
membar_acquire();
}
static void
shmif_unlockbus(struct shmif_mem *busmem)
{
unsigned int old __diagused;
membar_release();
old = atomic_swap_32(&busmem->shm_lock, LOCK_UNLOCKED);
KASSERT(old == LOCK_LOCKED);
}
if (sc->sc_busmem->shm_magic
&& sc->sc_busmem->shm_magic != SHMIF_MAGIC) {
printf("bus is not magical");
rumpuser_unmap(sc->sc_busmem, BUSMEM_SIZE);
return ENOEXEC;
}
/*
* Prefault in pages to minimize runtime penalty with buslock.
* Use 512 instead of PAGE_SIZE to make sure we catch cases where
* rump kernel PAGE_SIZE > host page size.
*/
for (p = (uint8_t *)sc->sc_busmem;
p < (uint8_t *)sc->sc_busmem + BUSMEM_SIZE;
p += 512)
v = *p;
shmif_lockbus(sc->sc_busmem);
/* we're first? initialize bus */
if (sc->sc_busmem->shm_magic == 0) {
sc->sc_busmem->shm_magic = SHMIF_MAGIC;
sc->sc_busmem->shm_first = BUSMEM_DATASIZE;
}
static int
shmif_clone(struct if_clone *ifc, int unit)
{
int rc __diagused;
vmem_addr_t unit2;
/*
* Ok, we know the unit number, but we must still reserve it.
* Otherwise the wildcard-side of things might get the same one.
* This is slightly offset-happy due to vmem. First, we offset
* the range of unit numbers by +1 since vmem cannot deal with
* ranges starting from 0. Talk about uuuh.
*/
rc = vmem_xalloc(shmif_units, 1, 0, 0, 0, unit+1, unit+1,
VM_SLEEP | VM_INSTANTFIT, &unit2);
KASSERT(rc == 0 && unit2-1 == unit);
if (sc->sc_rcvl)
kthread_join(sc->sc_rcvl);
sc->sc_rcvl = NULL;
/*
* Need to be called after the kthread left, otherwise closing kqueue
* (sc_kq) hangs sometimes perhaps because of a race condition between
* close and kevent in the kthread on the kqueue.
*/
finibackend(sc);
/*
* Compare with DOWN to allow UNKNOWN (the default value),
* which is required by some ATF tests using rump servers
* written in C.
*/
if (ifp->if_link_state == LINK_STATE_DOWN)
goto dontsend;
/*
* wakeup thread. this will of course wake up all bus
* listeners, but that's life.
*/
if (sc->sc_memfd != -1) {
dowakeup(sc);
}
}
/*
* Check if we have been sleeping too long. Basically,
* our in-sc nextpkt must by first <= nextpkt <= last"+1".
* We use the fact that first is guaranteed to never overlap
* with the last frame in the ring.
*/
static __inline bool
stillvalid_p(struct shmif_sc *sc)
{
struct shmif_mem *busmem = sc->sc_busmem;
unsigned gendiff = busmem->shm_gen - sc->sc_devgen;
uint32_t lastoff, devoff;
/*
* If our read pointer is ahead the bus last write, our
* generation must be one behind.
*/
KASSERT(!(nextpkt > busmem->shm_last
&& sc->sc_devgen == busmem->shm_gen));
if (wrap) {
sc->sc_devgen++;
DPRINTF(("dev %p generation now %" PRIu64 "\n",
sc, sc->sc_devgen));
}
/*
* Ignore packets too short to possibly be valid.
* This is hit at least for the first frame on a new bus.
*/
if (__predict_false(sp.sp_len < ETHER_HDR_LEN)) {
DPRINTF(("shmif read packet len %d < ETHER_HDR_LEN\n",
sp.sp_len));
continue;
}
/*
* Test if we want to pass the packet upwards
*/
eth = mtod(m, struct ether_header *);
/*
* Compare with DOWN to allow UNKNOWN (the default value),
* which is required by some ATF tests using rump servers
* written in C.
*/
if (ifp->if_link_state == LINK_STATE_DOWN) {
passup = false;
} else if (sp.sp_sender == sc->sc_uid) {
passup = false;
} else if (memcmp(eth->ether_dhost, CLLADDR(ifp->if_sadl),
ETHER_ADDR_LEN) == 0) {
passup = true;
} else if (ETHER_IS_MULTICAST(eth->ether_dhost)) {
passup = true;
} else if (ifp->if_flags & IFF_PROMISC) {
m->m_flags |= M_PROMISC;
passup = true;
} else {
passup = false;
}
if (passup) {
int bound;
m = ether_sw_offload_rx(ifp, m);
KERNEL_LOCK(1, NULL);
/* Prevent LWP migrations between CPUs for psref(9) */
bound = curlwp_bind();
if_input(ifp, m);
curlwp_bindx(bound);
KERNEL_UNLOCK_ONE(NULL);
m = NULL;
}
/* else: reuse mbuf for a future packet */
}
m_freem(m);
m = NULL;