/* * \brief LwIP netif for the Nic session * \author Emery Hemingway * \date 2016-09-28 * * If you want to use the lwIP API in a native Genode * component then this is the Nic client to use. */ /* * Copyright (C) 2016-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ /* TODO: Bugs * tcp_out.c:1410 0x1033d4c - GPF * tcp_in.c:1102 0x102c084 - GPF * tcp_out.c:1335 - GPF * OUT_OF_CAPS * PAGE_FAULT and GPFs on task->execute * GPF on task->next() */ #ifndef __LWIP__NIC_NETIF_H__ #define __LWIP__NIC_NETIF_H__ #include "base/allocator.h" #include "mx/memory/global_heap.h" #include #if ETH_PAD_SIZE #error ETH_PAD_SIZE defined but unsupported by lwip/nic_netif.h #endif #ifndef __cplusplus #error lwip/nic_netif.h is a C++ only header #endif /* Genode includes */ #include #include #include /* MxTasking includes */ #include #include /* EalanOS includes */ #include namespace Lwip { extern "C" { /* LwIP includes */ #include #include #if LWIP_IPV6 #include #endif #include #include #include } } // namespace Lwip namespace Mxip { class Receive_task; class Tx_ready_task; class Link_state_task; class Finished_rx_task; class Nic_netif; using namespace Lwip; } namespace Lwip { extern "C" { static void nic_netif_pbuf_free(pbuf *p); static err_t nic_netif_init(struct netif *netif); static err_t nic_netif_linkoutput(struct netif *netif, struct pbuf *p); static void nic_netif_status_callback(struct netif *netif); } /** * Metadata for packet backed pbufs */ struct Nic_netif_pbuf { struct pbuf_custom p { }; Mxip::Nic_netif &netif; void *payload; Nic_netif_pbuf(Mxip::Nic_netif &nic, void *pl) : netif(nic), payload(pl) { p.custom_free_function = nic_netif_pbuf_free; } }; } class Mxip::Nic_netif { public: friend class Mxip::Receive_task; friend class Mxip::Tx_ready_task; friend class Mxip::Link_state_task; struct Wakeup_scheduler : Genode::Noncopyable, Genode::Interface { virtual void schedule_nic_server_wakeup() = 0; }; enum { TASK_SIZE = 128, SB_SIZE = 2 * 2048 }; private: Genode::Entrypoint _ep; Wakeup_scheduler &_wakeup_scheduler; bool _tx_saturated = false; enum { PACKET_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE, BUF_SIZE = 2048*PACKET_SIZE, }; Genode::Tslab _pbuf_alloc; public: using Payload_allocator = Ealan::Memory::Hamstraaja; private: Ealan::Memory::Hamstraaja<128, 4 * 4096> *_task_alloc{nullptr}; Payload_allocator *_packet_alloc{nullptr}; Nic::Packet_allocator _nic_tx_alloc; Nic::Connection _nic; struct Lwip::netif _netif { }; Lwip::ip_addr_t ip { }; Lwip::ip_addr_t nm { }; Lwip::ip_addr_t gw { }; Genode::Io_signal_handler _link_state_handler; Genode::Io_signal_handler _rx_packet_handler; Genode::Io_signal_handler _tx_ready_handler; bool _dhcp { false }; public: void free_pbuf(Lwip::Nic_netif_pbuf &pbuf) { /*bool msg_once = true; while (!_nic.rx()->ready_to_ack()) { if (msg_once) Genode::error("Nic rx acknowledgement queue congested, blocking to free pbuf"); msg_once = false; _ep.wait_and_dispatch_one_io_signal(); } _nic.rx()->try_ack_packet(pbuf.packet); _wakeup_scheduler.schedule_nic_server_wakeup();*/ _packet_alloc->free(pbuf.payload); _task_alloc->free(&pbuf);//destroy(_pbuf_alloc, &pbuf); } /************************* ** Nic signal handlers ** *************************/ void handle_link_state(); void handle_rx_packets(); /** * Handle tx ack_avail and ready_to_submit signals */ void handle_tx_ready(); void configure(Genode::Xml_node const &config) { _dhcp = config.attribute_value("dhcp", false); using Str = Genode::String; Str ip_str = config.attribute_value("ip_addr", Str()); Genode::log("Static IP: ", ip_str); if (_dhcp && ip_str != "") { _dhcp = false; Lwip::netif_set_down(&_netif); Genode::error("refusing to configure lwIP interface with both DHCP and a static IPv4 address"); return; } Genode::log("Bringing up NIC"); Lwip::netif_set_up(&_netif); Genode::log("Set NIC up"); if (ip_str != "") { Lwip::ip_addr_t ipaddr; if (!Lwip::ipaddr_aton(ip_str.string(), &ipaddr)) { Genode::error("lwIP configured with invalid IP address '",ip_str,"'"); throw ip_str; } Genode::log("Setting IP for NIC"); netif_set_ipaddr(&_netif, ip_2_ip4(&ipaddr)); Genode::log("Set IP address for NIC"); if (config.has_attribute("netmask")) { Str str = config.attribute_value("netmask", Str()); Lwip::ip_addr_t ip; Lwip::ipaddr_aton(str.string(), &ip); Lwip::netif_set_netmask(&_netif, ip_2_ip4(&ip)); } if (config.has_attribute("gateway")) { Str str = config.attribute_value("gateway", Str()); Lwip::ip_addr_t ip; Lwip::ipaddr_aton(str.string(), &ip); Lwip::netif_set_gw(&_netif, ip_2_ip4(&ip)); } } if (config.has_attribute("nameserver")) { /* * LwIP does not use DNS internally, but the application * should expect "dns_getserver" to work regardless of * how the netif configures itself. */ Str str = config.attribute_value("nameserver", Str()); Lwip::ip_addr_t ip; Lwip::ipaddr_aton(str.string(), &ip); //dns_setserver(0, &ip); } Genode::log("Finished configuring NIC"); handle_link_state(); } Nic_netif(Genode::Env &env, Genode::Allocator &alloc, Genode::Xml_node config, Wakeup_scheduler &wakeup_scheduler, Ealan::Memory::Hamstraaja<128, 4 * 4096> *task_alloc, Payload_allocator *palloc) : _ep(env, 4*4096, "mxip_ep", env.cpu().affinity_space().location_of_index(0)), _wakeup_scheduler(wakeup_scheduler), _pbuf_alloc(alloc), _task_alloc(task_alloc), _packet_alloc(palloc), _nic_tx_alloc(&alloc), _nic(env, &_nic_tx_alloc, BUF_SIZE, BUF_SIZE, config.attribute_value("label", Genode::String<160>("mxip")).string()), _link_state_handler(_ep, *this, &Nic_netif::handle_link_state), _rx_packet_handler( _ep, *this, &Nic_netif::handle_rx_packets), _tx_ready_handler(_ep, *this, &Nic_netif::handle_tx_ready) { Genode::memset(&_netif, 0x00, sizeof(_netif)); { Lwip::ip4_addr_t v4dummy; IP4_ADDR(&v4dummy, 0, 0, 0, 0); netif* r = Lwip::netif_add(&_netif, &v4dummy, &v4dummy, &v4dummy, this, nic_netif_init, ethernet_input); if (r == NULL) { Genode::error("failed to initialize Nic to lwIP interface"); throw r; } } Lwip::netif_set_default(&_netif); Lwip::netif_set_status_callback( &_netif, nic_netif_status_callback); Lwip::nic_netif_status_callback(&_netif); configure(config); } virtual ~Nic_netif() { } Lwip::netif& lwip_netif() { return _netif; } bool tx_saturated() const { return _tx_saturated; } /** * Status callback to override in subclass */ virtual void status_callback() { } /** * Callback issued by lwIP to initialize netif struct * * \noapi */ err_t init() { /* * XXX: hostname and MTU could probably be * set in the Nic client constructor */ #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ _netif.hostname = ""; #endif /* LWIP_NETIF_HOSTNAME */ _netif.name[0] = 'e'; _netif.name[1] = 'n'; _netif.output = etharp_output; #if LWIP_IPV6 _netif.output_ip6 = ethip6_output; #endif /* LWIP_IPV6 */ _netif.linkoutput = nic_netif_linkoutput; /* Set physical MAC address */ Nic::Mac_address const mac = _nic.mac_address(); for(int i=0; i<6; ++i) _netif.hwaddr[i] = mac.addr[i]; _netif.mtu = 1500; /* XXX: just a guess */ _netif.hwaddr_len = ETHARP_HWADDR_LEN; _netif.flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* set Nic session signal handlers */ _nic.link_state_sigh(_link_state_handler); _nic.rx_channel()->sigh_packet_avail(_rx_packet_handler); _nic.rx_channel()->sigh_ready_to_ack(_rx_packet_handler); _nic.tx_channel()->sigh_ready_to_submit(_tx_ready_handler); _nic.tx_channel()->sigh_ack_avail (_tx_ready_handler); return ERR_OK; } /** * Callback issued by lwIP to write a Nic packet * * \noapi */ err_t linkoutput(struct pbuf *p) { auto &tx = *_nic.tx(); /* flush acknowledgements */ while (tx.ack_avail()) { tx.release_packet(tx.try_get_acked_packet()); _tx_saturated = false; } if (!tx.ready_to_submit()) { Genode::error("lwIP: Nic packet queue congested, cannot send packet"); _tx_saturated = true; return ERR_WOULDBLOCK; } Nic::Packet_descriptor packet; try { packet = tx.alloc_packet(p->tot_len); } catch (...) { Genode::error("lwIP: Nic packet allocation failed, cannot send packet"); _tx_saturated = true; return ERR_WOULDBLOCK; } /* * We iterate over the pbuf chain until we have read the entire * pbuf into the packet. */ char *dst = tx.packet_content(packet); for (struct pbuf *q = p; q != nullptr; q = q->next) { char const *src = (char const *)q->payload; Genode::memcpy(dst, src, q->len); dst += q->len; } tx.try_submit_packet(packet); _wakeup_scheduler.schedule_nic_server_wakeup(); LINK_STATS_INC(link.xmit); return ERR_OK; } bool ready() { return netif_is_up(&_netif) && !ip_addr_isany(&_netif.ip_addr); } /** * Trigger submission of deferred packet-stream signals */ void wakeup_nic_server() { _nic.rx()->wakeup(); _nic.tx()->wakeup(); } }; /************************** ** MxIP handler tasks ** **************************/ class Mxip::Receive_task : public mx::tasking::TaskInterface { public: Receive_task(void *payload, size_t len, struct Lwip::netif &netif, Mxip::Nic_netif &net) : _netif(netif), _payload(payload), _length(len), _net(net) { } mx::tasking::TaskResult execute(std::uint16_t, std::uint16_t) override { Lwip::Nic_netif_pbuf *nic_pbuf = new (_net._task_alloc->alloc(sizeof(Lwip::Nic_netif_pbuf))) Lwip::Nic_netif_pbuf(_net, _payload); if (!nic_pbuf) { Genode::warning("Could not allocate pbuf "); _net._task_alloc->free(this); return mx::tasking::TaskResult::make_null(); } pbuf *p = pbuf_alloced_custom(PBUF_RAW, _length, PBUF_REF, &nic_pbuf->p, _payload, _length); LINK_STATS_INC(link.recv); if (!p) { Genode::warning("Initialization of pbuf failed."); _net._task_alloc->free(this); return mx::tasking::TaskResult::make_null(); } err_t rc = _netif.input(p, &_netif); if (rc != ERR_OK) { Genode::error("error forwarding Nic packet to lwIP: error=", static_cast(rc)); pbuf_free(p); } _net._task_alloc->free(this); return mx::tasking::TaskResult::make_null(); //return mx::tasking::TaskResult::make_remove(); } private: struct Lwip::netif &_netif; void *_payload; std::size_t _length; Mxip::Nic_netif &_net; }; class Mxip::Tx_ready_task : public mx::tasking::TaskInterface { public: Tx_ready_task(Nic::Connection &nic, Mxip::Nic_netif &netif) : _nic(nic), _netif(netif) { } mx::tasking::TaskResult execute(std::uint16_t, std::uint16_t) override { auto &tx = *_nic.tx(); bool progress = false; while (tx.ack_avail()) { tx.release_packet(tx.try_get_acked_packet()); _netif._tx_saturated = false; progress = true; } _netif.status_callback(); if (progress) _netif._wakeup_scheduler.schedule_nic_server_wakeup(); _netif._task_alloc->free(this); return mx::tasking::TaskResult::make_null(); //return mx::tasking::TaskResult::make_remove(); } private: Nic::Connection &_nic; Mxip::Nic_netif &_netif; }; class Mxip::Link_state_task : public mx::tasking::TaskInterface { public: Link_state_task(Nic::Connection &nic, Lwip::netif &netif, Mxip::Nic_netif &nic_netif, bool dhcp) : _nic(nic), _nic_netif(nic_netif), _netif(netif), _dhcp(dhcp) { } mx::tasking::TaskResult execute(std::uint16_t, std::uint16_t) override { /* * if the application wants to be informed of the * link state then it should use 'set_link_callback' */ if (_nic.link_state()) { Lwip::netif_set_link_up(&_netif); if (_dhcp) { err_t err = dhcp_start(&_netif); if (err != ERR_OK) { Genode::error("failed to configure lwIP interface with DHCP, error ", -err); } } else { Lwip::dhcp_inform(&_netif); } } else { Lwip::netif_set_link_down(&_netif); if (_dhcp) { Lwip::dhcp_release_and_stop(&_netif); } } _nic_netif._task_alloc->free(this); return mx::tasking::TaskResult::make_null(); //return mx::tasking::TaskResult::make_remove(); } private: Nic::Connection &_nic; Mxip::Nic_netif &_nic_netif; Lwip::netif &_netif; bool _dhcp; }; /************************** ** LwIP netif callbacks ** **************************/ namespace Lwip { extern "C" { /** * Free a packet buffer backed pbuf */ static void nic_netif_pbuf_free(pbuf *p) { Nic_netif_pbuf *nic_pbuf = reinterpret_cast(p); nic_pbuf->netif.free_pbuf(*nic_pbuf); } /** * Initialize the netif */ static err_t nic_netif_init(struct netif *netif) { Mxip::Nic_netif *nic_netif = (Mxip::Nic_netif *)netif->state; return nic_netif->init(); } /** * Send a raw packet to the Nic session */ static err_t nic_netif_linkoutput(struct netif *netif, struct pbuf *p) { Mxip::Nic_netif *nic_netif = (Mxip::Nic_netif *)netif->state; return nic_netif->linkoutput(p); } static void nic_netif_status_callback(struct netif *netif) { Mxip::Nic_netif *nic_netif = (Mxip::Nic_netif *)netif->state; if (netif_is_up(netif)) { if (!ip4_addr_isany(netif_ip4_addr(netif))) { using Str = Genode::String; Str address((char const*)ip4addr_ntoa(netif_ip4_addr(netif))); Str netmask((char const*)ip4addr_ntoa(netif_ip4_netmask(netif))); Str gateway((char const*)ip4addr_ntoa(netif_ip4_gw(netif))); Genode::log("MxIP Nic interface up" " address=", address, " netmask=", netmask, " gateway=", gateway); } } else { Genode::log("MxIP Nic interface down"); } //nic_netif->status_callback(); } } } #endif /* __LWIP__NIC_NETIF_H__ */