l4re-base-25.08.0

This commit is contained in:
2025-09-12 15:55:45 +02:00
commit d959eaab98
37938 changed files with 9382688 additions and 0 deletions

View File

@@ -0,0 +1,827 @@
/*
* Copyright (C) 2016-2020, 2022-2024 Kernkonzept GmbH.
* Author(s): Jean Wolter <jean.wolter@kernkonzept.com>
* Manuel von Oltersdorff-Kalettka <manuel.kalettka@kernkonzept.com>
*
* License: see LICENSE.spdx (in this directory or the directories above)
*/
#include <l4/re/util/meta>
#include <l4/re/util/object_registry>
#include <l4/re/util/br_manager>
#include <l4/sys/factory>
#include <l4/sys/task>
#include <l4/sys/cxx/ipc_epiface>
#include <l4/sys/cxx/ipc_varg>
#include <l4/cxx/dlist>
#include <l4/cxx/string>
#include <stdlib.h>
#include <string>
#include <terminate_handler-l4>
#include <vector>
#include "debug.h"
#include "options.h"
#include "switch.h"
#include "vlan.h"
#include <l4/virtio-net-switch/stats.h>
/**
* \defgroup virtio_net_switch Virtio Net Switch
*
* A virtual network switch that can be used as defined in the virtio protocol.
*
* The abstraction of a single connection with a network device (also called
* client) from the switch's perspective is a port. A client can register
* multiple ports on the switch. The communication between a client and the
* switch happens via IRQs, MMIO and shared memory as defined by the Virtio
* protocol. The switch supports VLANs and ports can be either 'access' or
* 'trunk' ports.
* The optionally available monitor port receives network traffic from all
* ports, and the monitor can not send.
*
* \{
*/
/*
* Registry for our server, used to register
* - factory capability
* - irq object for capability deletion irqs
* - virtio host kick irqs
*/
static L4Re::Util::Registry_server<L4Re::Util::Br_manager_hooks> server;
using Ds_vector = std::vector<L4::Cap<L4Re::Dataspace>>;
static std::shared_ptr<Ds_vector> trusted_dataspaces;
static bool
parse_int_param(L4::Ipc::Varg const &param, char const *prefix, int *out)
{
l4_size_t headlen = strlen(prefix);
if (param.length() < headlen)
return false;
char const *pstr = param.value<char const *>();
if (strncmp(pstr, prefix, headlen) != 0)
return false;
std::string tail(pstr + headlen, param.length() - headlen);
if (!parse_int_optstring(tail.c_str(), out))
{
Err(Err::Normal).printf("Bad parameter '%s'. Invalid number specified.\n",
prefix);
throw L4::Runtime_error(-L4_EINVAL);
}
return true;
}
static void
assign_random_mac(l4_uint8_t mac[6])
{
static bool initialized = false;
if (!initialized)
{
srandom(l4_kip_clock(l4re_kip()));
initialized = true;
}
for (int i = 0; i < 6; i++)
mac[i] = static_cast<l4_uint8_t>(random());
mac[0] &= ~(1U << 0); // clear multicast bit
mac[0] |= 1U << 1; // set "locally administered" bit
}
/**
* The IPC interface for creating ports.
*
* The Switch factory provides an IPC interface to create ports. Ports are
* the only option for a client to communicate with the switch and, thus, with
* other network devices.
*
* The `Switch_factory` gets constructed when the net switch application gets
* started. It thereafter gets registered on the switch's server to serve IPC
* `create` calls.
*/
class Switch_factory : public L4::Epiface_t<Switch_factory, L4::Factory>
{
/**
* Implement the generic irq related part of the port
*/
class Port : public L4virtio_port
{
// Irq used to notify the guest
L4::Cap<L4::Irq> _device_notify_irq;
L4::Cap<L4::Irq> device_notify_irq() const override
{ return _device_notify_irq; }
public:
Port(unsigned vq_max, unsigned num_ds, char const *name,
l4_uint8_t const *mac)
: L4virtio_port(vq_max, num_ds, name, mac) {}
/** register the host IRQ and the port itself on the switch's server */
void register_end_points(L4Re::Util::Object_registry* registry,
L4::Epiface *kick_irq)
{
// register virtio host kick irq
_device_notify_irq = L4Re::chkcap(registry->register_irq_obj(kick_irq));
// register virtio endpoint
L4Re::chkcap(registry->register_obj(this));
// decrement ref counter to get a notification when the last
// external reference vanishes
obj_cap()->dec_refcnt(1);
}
virtual ~Port()
{ server.registry()->unregister_obj(this); }
};
/**
* Implement the irq related part of a switched port
*/
class Switch_port : public Port
{
/**
* IRQ endpoint on the port.
*
* Each port holds its own IRQ that gets triggered by the client whenever
* there is a new outgoing request in the port's transmission queue or when
* there is new space in the port's receive queue.
*
* A `Kick_irq` is constructed on port creation. At this time, it also gets
* registered on the switch's server.
*/
class Kick_irq : public L4::Irqep_t<Kick_irq>
{
Virtio_switch *_switch; /**< pointer to the net switch */
L4virtio_port *_port; /**< pointer to the associated port */
public:
/**
* Callback for the IRQ
*
* This function redirects the call to `Virtio_switch::handle_l4virtio_port_tx`,
* since the port cannot finish a transmission on its own.
*/
void handle_irq()
{ _switch->handle_l4virtio_port_tx(_port); }
Kick_irq(Virtio_switch *virtio_switch, L4virtio_port *port)
: _switch{virtio_switch}, _port{port} {}
};
Kick_irq _kick_irq; /**< The IRQ to notify the client. */
Kick_irq _reschedule_tx_irq;
public:
Switch_port(L4Re::Util::Object_registry *registry,
Virtio_switch *virtio_switch, unsigned vq_max, unsigned num_ds,
char const *name, l4_uint8_t const *mac)
: Port(vq_max, num_ds, name, mac),
_kick_irq(virtio_switch, this),
_reschedule_tx_irq(virtio_switch, this)
{
register_end_points(registry, &_kick_irq);
_pending_tx_reschedule =
L4Re::chkcap(registry->register_irq_obj(&_reschedule_tx_irq),
"Register TX reschedule IRQ.");
_pending_tx_reschedule->unmask();
}
virtual ~Switch_port()
{
// We need to delete the IRQ object created in register_irq_obj() ourselves
L4::Cap<L4::Task>(L4Re::This_task)
->unmap(_kick_irq.obj_cap().fpage(),
L4_FP_ALL_SPACES | L4_FP_DELETE_OBJ);
server.registry()->unregister_obj(&_kick_irq);
L4::Cap<L4::Task>(L4Re::This_task)
->unmap(_pending_tx_reschedule.fpage(),
L4_FP_ALL_SPACES | L4_FP_DELETE_OBJ);
server.registry()->unregister_obj(&_reschedule_tx_irq);
}
};
/**
* Implement the irq related part of a monitor port
*/
class Monitor_port : public Port
{
/**
* Handle incoming irqs by
* - handling pending outgoing requests
* - dropping all incoming requests
*/
class Kick_irq : public L4::Irqep_t<Kick_irq>
{
L4virtio_port *_port;
public:
/**
* Callback for the IRQ
*
* A Monitor port processes only requests on its receive queue and drops
* all requests on the transmit queue since it is not supposed to send
* network request.
*/
void handle_irq()
{
do
{
_port->tx_q()->disable_notify();
_port->rx_q()->disable_notify();
_port->drop_requests();
_port->tx_q()->enable_notify();
_port->rx_q()->enable_notify();
L4virtio::wmb();
L4virtio::rmb();
}
while (_port->tx_work_pending());
}
Kick_irq(L4virtio_port *port) : _port{port} {}
};
Kick_irq _kick_irq;
public:
Monitor_port(L4Re::Util::Object_registry* registry,
unsigned vq_max, unsigned num_ds, char const *name,
l4_uint8_t const *mac)
: Port(vq_max, num_ds, name, mac), _kick_irq(this)
{ register_end_points(registry, &_kick_irq); }
virtual ~Monitor_port()
{
// We need to delete the IRQ object created in register_irq_obj() ourselves
L4::Cap<L4::Task>(L4Re::This_task)
->unmap(_kick_irq.obj_cap().fpage(),
L4_FP_ALL_SPACES | L4_FP_DELETE_OBJ);
server.registry()->unregister_obj(&_kick_irq);
}
};
/**
* Implement the handler for the statistics reader capability.
*/
class Stats_reader
: public cxx::D_list_item,
public L4::Epiface_t<Stats_reader, Virtio_net_switch::Statistics_if>
{
L4Re::Util::Unique_cap<L4Re::Dataspace> _ds;
l4_addr_t _addr;
public:
Stats_reader()
{
l4_size_t size = Switch_statistics::get_instance().size();
_ds = L4Re::Util::make_unique_cap<L4Re::Dataspace>();
L4Re::chksys(L4Re::Env::env()->mem_alloc()->alloc(size, _ds.get()),
"Could not allocate shared mem ds.");
L4Re::chksys(L4Re::Env::env()->rm()->attach(&_addr, _ds->size(),
L4Re::Rm::F::Search_addr
| L4Re::Rm::F::RW,
L4::Ipc::make_cap_rw(_ds.get())));
memset(reinterpret_cast<void*>(_addr), 0, _ds->size());
}
~Stats_reader()
{
L4Re::Env::env()->rm()->detach(reinterpret_cast<l4_addr_t>(_addr), 0);
server.registry()->unregister_obj(this);
}
long op_get_buffer(Virtio_net_switch::Statistics_if::Rights,
L4::Ipc::Cap<L4Re::Dataspace> &ds)
{
// We hand out the dataspace in a read only manner. Clients must not be
// able to modify information as that would create an unwanted data
// channel.
ds = L4::Ipc::Cap<L4Re::Dataspace>(_ds.get(), L4_CAP_FPAGE_RO);
return L4_EOK;
}
long op_sync(Virtio_net_switch::Statistics_if::Rights)
{
memcpy(reinterpret_cast<void *>(_addr),
reinterpret_cast<void *>(Switch_statistics::get_instance().stats()),
Switch_statistics::get_instance().size());
return L4_EOK;
}
bool is_valid()
{ return obj_cap() && obj_cap().validate().label(); }
};
class Stats_reader_list
{
cxx::D_list<Stats_reader> _readers;
public:
void check_readers()
{
auto it = _readers.begin();
while (it != _readers.end())
{
auto *reader = *it;
if (!reader->is_valid())
{
it = _readers.erase(it);
delete reader;
}
else
++it;
}
}
void push_back(cxx::unique_ptr<Stats_reader> reader)
{
_readers.push_back(reader.release());
}
};
/*
* Handle vanishing caps by telling the switch that a port might have gone
*/
struct Del_cap_irq : public L4::Irqep_t<Del_cap_irq>
{
public:
void handle_irq()
{
_switch->check_ports();
_stats_readers->check_readers();
}
Del_cap_irq(Virtio_switch *virtio_switch, Stats_reader_list *stats_readers)
: _switch{virtio_switch},
_stats_readers{stats_readers}
{}
private:
Virtio_switch *_switch;
Stats_reader_list *_stats_readers;
};
Virtio_switch *_virtio_switch; /**< pointer to the actual net switch object */
/** maximum number of entries in a new virtqueueue created for a port */
unsigned _vq_max_num;
Stats_reader_list _stats_readers;
Del_cap_irq _del_cap_irq;
/**
* Evaluate an optional argument
*
* \param opt Optional argument.
* \param[out] monitor Set to true if argument is "type=monitor".
* \param name Pointer to name.
* \param size Size of name.
* \param[out] vlan_access Id of VLAN access port if "vlan=access=<id>" is
* present.
* \param[out] vlan_trunk List of VLANs if "vlan=trunk=[<id>[,<id]*] is
* present.
* \param[out] vlan_trunk_all
* Iff true, trunk port shall participate in all
* VLANs. vlan_trunk will be ignored.
*/
bool handle_opt_arg(L4::Ipc::Varg const &opt, bool &monitor,
char *name, size_t size,
l4_uint16_t &vlan_access,
std::vector<l4_uint16_t> &vlan_trunk,
bool *vlan_trunk_all,
l4_uint8_t mac[6], bool &mac_set)
{
assert(opt.is_of<char const *>());
unsigned len = opt.length();
const char *opt_str = opt.data();
Err err(Err::Normal);
if (len > 5)
{
if (!strncmp("type=", opt_str, 5))
{
if (!strncmp("type=monitor", opt_str, len))
{
monitor = true;
return true;
}
else if (!strncmp("type=none", opt_str, len))
return true;
err.printf("Unknown type '%.*s'\n", opt.length() - 5, opt.data() + 5);
return false;
}
else if (!strncmp("name=", opt_str, 5))
{
snprintf(name, size, "%.*s", opt.length() - 5, opt.data() + 5);
return true;
}
else if (!strncmp("vlan=", opt_str, 5))
{
cxx::String str(opt_str + 5, strnlen(opt_str + 5, len - 5));
cxx::String::Index idx;
if ((idx = str.starts_with("access=")))
{
str = str.substr(idx);
l4_uint16_t vid;
int next = str.from_dec(&vid);
if (next && next == str.len() && vlan_valid_id(vid))
vlan_access = vid;
else
{
err.printf("Invalid VLAN access port id '%.*s'\n",
opt.length(), opt.data());
return false;
}
}
else if ((idx = str.starts_with("trunk=")))
{
int next;
l4_uint16_t vid;
str = str.substr(idx);
if (str == cxx::String("all"))
{
*vlan_trunk_all = true;
return true;
}
while ((next = str.from_dec(&vid)))
{
if (!vlan_valid_id(vid))
break;
vlan_trunk.push_back(vid);
if (next < str.len() && str[next] != ',')
break;
str = str.substr(next+1);
}
if (vlan_trunk.empty() || !str.empty())
{
err.printf("Invalid VLAN trunk port spec '%.*s'\n",
opt.length(), opt.data());
return false;
}
}
else
{
err.printf("Invalid VLAN specification..\n");
return false;
}
return true;
}
else if (!strncmp("mac=", opt_str, 4))
{
size_t const OPT_LEN = 4 /* mac= */ + 6*2 /* digits */ + 5 /* : */;
// expect NUL terminated string for simplicity
if (len > OPT_LEN && opt_str[OPT_LEN] == '\0' &&
sscanf(opt_str+4, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0],
&mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6)
{
mac_set = true;
return true;
}
err.printf("Invalid mac address '%.*s'\n", len - 4, opt_str + 4);
return false;
}
}
err.printf("Unknown option '%.*s'\n", opt.length(), opt.data());
return false;
}
public:
Switch_factory(Virtio_switch *virtio_switch, unsigned vq_max_num)
: _virtio_switch{virtio_switch}, _vq_max_num{vq_max_num},
_del_cap_irq{virtio_switch, &_stats_readers}
{
auto c = L4Re::chkcap(server.registry()->register_irq_obj(&_del_cap_irq));
L4Re::chksys(L4Re::Env::env()->main_thread()->register_del_irq(c));
};
/**
* Handle factory protocol
*
* This function is invoked after an incoming factory::create
* request and creates a new port or statistics interface if possible.
*/
long op_create(L4::Factory::Rights, L4::Ipc::Cap<void> &res,
l4_umword_t type, L4::Ipc::Varg_list_ref va)
{
switch (type)
{
case 0:
return create_port(res, va);
case 1:
return create_stats(res);
default:
Dbg(Dbg::Core, Dbg::Warn).printf("op_create: Invalid object type\n");
return -L4_EINVAL;
}
}
long create_port(L4::Ipc::Cap<void> &res, L4::Ipc::Varg_list_ref va)
{
Dbg warn(Dbg::Port, Dbg::Warn, "Port");
Dbg info(Dbg::Port, Dbg::Info, "Port");
info.printf("Incoming port request\n");
bool monitor = false;
char name[20] = "";
unsigned arg_n = 2;
l4_uint16_t vlan_access = 0;
std::vector<l4_uint16_t> vlan_trunk;
bool vlan_trunk_all = false;
l4_uint8_t mac[6];
bool mac_set = false;
int num_ds = 2;
for (L4::Ipc::Varg opt: va)
{
if (!opt.is_of<char const *>())
{
warn.printf("Unexpected type for argument %d\n", arg_n);
return -L4_EINVAL;
}
if (parse_int_param(opt, "ds-max=", &num_ds))
{
if (num_ds <= 0 || num_ds > 80)
{
Err(Err::Normal).printf("warning: client requested invalid number"
" of data spaces: 0 < %d <= 80\n", num_ds);
return -L4_EINVAL;
}
}
else if (!handle_opt_arg(opt, monitor, name, sizeof(name), vlan_access,
vlan_trunk, &vlan_trunk_all, mac, mac_set))
return -L4_EINVAL;
++arg_n;
}
int port_num = _virtio_switch->port_available(monitor);
if (port_num < 0)
{
warn.printf("No port available\n");
return -L4_ENOMEM;
}
if (vlan_access && (!vlan_trunk.empty() || vlan_trunk_all))
{
warn.printf("VLAN port cannot be access and trunk simultaneously.\n");
return -L4_EINVAL;
}
if (!name[0])
snprintf(name, sizeof(name), "%s[%d]", monitor ? "monitor" : "",
port_num);
info.printf(" Creating port %s%s\n", name,
monitor ? " as monitor port" : "");
// Assign a random MAC address if we assign one to our devices but the
// user has not passed an explicit one for a port.
if (!mac_set && Options::get_options()->assign_mac())
assign_random_mac(mac);
l4_uint8_t *mac_ptr = (mac_set || Options::get_options()->assign_mac())
? mac : nullptr;
// create port
Port *port;
if (monitor)
{
port = new Monitor_port(server.registry(), _vq_max_num, num_ds, name,
mac_ptr);
port->set_monitor();
if (vlan_access)
warn.printf("vlan=access=<id> ignored on monitor ports!\n");
if (!vlan_trunk.empty())
warn.printf("vlan=trunk=... ignored on monitor ports!\n");
}
else
{
port = new Switch_port(server.registry(), _virtio_switch, _vq_max_num,
num_ds, name, mac_ptr);
if (vlan_access)
port->set_vlan_access(vlan_access);
else if (vlan_trunk_all)
port->set_vlan_trunk_all();
else if (!vlan_trunk.empty())
port->set_vlan_trunk(vlan_trunk);
}
port->add_trusted_dataspaces(trusted_dataspaces);
if (!trusted_dataspaces->empty())
port->enable_trusted_ds_validation();
// hand port over to the switch
bool added = monitor ? _virtio_switch->add_monitor_port(port)
: _virtio_switch->add_port(port);
if (!added)
{
delete port;
return -L4_ENOMEM;
}
res = L4::Ipc::make_cap(port->obj_cap(), L4_CAP_FPAGE_RWSD);
info.printf(" Created port %s\n", name);
return L4_EOK;
}
long create_stats(L4::Ipc::Cap<void> &res)
{
// Create a stats reader and throw away our reference to get a notification
// when the external reference vanishes.
auto reader = cxx::make_unique<Stats_reader>();
L4Re::chkcap(server.registry()->register_obj(reader.get()));
reader->obj_cap()->dec_refcnt(1);
res = L4::Ipc::make_cap(reader->obj_cap(),
L4_CAP_FPAGE_R | L4_CAP_FPAGE_D);
_stats_readers.push_back(cxx::move(reader));
return L4_EOK;
}
};
#if CONFIG_VNS_IXL
/**
* Implement the irq related part of an ixl port.
*/
class Ixl_hw_port : public Ixl_port
{
template<typename Derived>
class Port_irq : public L4::Irqep_t<Derived>
{
public:
Port_irq(Virtio_switch *virtio_switch, Ixl_port *port)
: _switch{virtio_switch}, _port{port} {}
protected:
Virtio_switch *_switch;
Ixl_port *_port;
};
class Receive_irq : public Port_irq<Receive_irq>
{
public:
using Port_irq::Port_irq;
/**
* Callback for the IRQ
*
* This function redirects the call to `Virtio_switch::handle_ixl_port_tx`,
* since the port cannot finish a transmission on its own.
*/
void handle_irq()
{
if (!_port->dev()->check_recv_irq(0))
return;
if (_switch->handle_ixl_port_tx(_port))
_port->dev()->ack_recv_irq(0);
}
};
class Reschedule_tx_irq : public Port_irq<Reschedule_tx_irq>
{
public:
using Port_irq::Port_irq;
void handle_irq()
{
if (_switch->handle_ixl_port_tx(_port))
// Entire TX queue handled, re-enable the recv IRQ again.
_port->dev()->ack_recv_irq(0);
}
};
Receive_irq _recv_irq;
Reschedule_tx_irq _reschedule_tx_irq;
public:
Ixl_hw_port(L4Re::Util::Object_registry *registry,
Virtio_switch *virtio_switch, Ixl::Ixl_device *dev)
: Ixl_port(dev),
_recv_irq(virtio_switch, this),
_reschedule_tx_irq(virtio_switch, this)
{
L4::Cap<L4::Irq> recv_irq_cap = L4Re::chkcap(dev->get_recv_irq(0), "Get receive IRQ");
L4Re::chkcap(registry->register_obj(&_recv_irq, recv_irq_cap),
"Register receive IRQ.");
recv_irq_cap->unmask();
_pending_tx_reschedule =
L4Re::chkcap(registry->register_irq_obj(&_reschedule_tx_irq),
"Register TX reschedule IRQ.");
_pending_tx_reschedule->unmask();
}
~Ixl_hw_port() override
{
server.registry()->unregister_obj(&_recv_irq);
}
};
static void
discover_ixl_devices(L4::Cap<L4vbus::Vbus> vbus, Virtio_switch *virtio_switch)
{
struct Ixl::Dev_cfg cfg;
// Configure the device in asynchronous notify mode.
cfg.irq_timeout_ms = -1;
// TODO: Support detecting multiple devices on a Vbus.
// Setup the driver (also resets and initializes the NIC).
Ixl::Ixl_device *dev = Ixl::Ixl_device::ixl_init(vbus, 0, cfg);
if (!dev)
// No Ixl supported device found, Ixl already printed an error message.
return;
Ixl_hw_port *hw_port = new Ixl_hw_port(server.registry(), virtio_switch, dev);
if (!virtio_switch->add_port(hw_port))
{
Err().printf("error adding ixl port\n");
delete hw_port;
}
}
#endif
int main(int argc, char *argv[])
{
trusted_dataspaces = std::make_shared<Ds_vector>();
auto *opts = Options::parse_options(argc, argv, trusted_dataspaces);
if (!opts)
{
Err().printf("Error during command line parsing.\n");
return 1;
}
// Show welcome message if debug level is not set to quiet
if (Dbg(Dbg::Core, Dbg::Warn).is_active())
printf("Hello from l4virtio switch\n");
Virtio_switch *virtio_switch = new Virtio_switch(opts->get_max_ports());
#ifdef CONFIG_VNS_STATS
Switch_statistics::get_instance().initialize(opts->get_max_ports());
#endif
#if CONFIG_VNS_IXL
auto vbus = L4Re::Env::env()->get_cap<L4vbus::Vbus>("vbus");
if (vbus.is_valid())
discover_ixl_devices(vbus, virtio_switch);
#endif
Switch_factory *factory = new Switch_factory(virtio_switch,
opts->get_virtq_max_num());
L4::Cap<void> cap = server.registry()->register_obj(factory, "svr");
if (!cap.is_valid())
{
Err().printf("error registering switch\n");
return 2;
}
/*
* server loop will handle 4 types of events
* - Switch_factory
* - factory protocol
* - capability deletion
* - delegated to Virtio_switch::check_ports()
* - Switch_factory::Switch_port
* - irqs triggered by clients
* - delegated to Virtio_switch::handle_l4virtio_port_tx()
* - Virtio_net_transfer
* - timeouts for pending transfer requests added by
* Port_iface::handle_request() via registered via
* L4::Epiface::server_iface()->add_timeout()
*/
server.loop();
return 0;
}
/**\}*/