
This commit adds a new TCP/IP service to MINIX 3. As its core, the service uses the lwIP TCP/IP stack for maintenance reasons. The service aims to be compatible with NetBSD userland, including its low-level network management utilities. It also aims to support modern features such as IPv6. In summary, the new LWIP service has support for the following main features: - TCP, UDP, RAW sockets with mostly standard BSD API semantics; - IPv6 support: host mode (complete) and router mode (partial); - most of the standard BSD API socket options (SO_); - all of the standard BSD API message flags (MSG_); - the most used protocol-specific socket and control options; - a default loopback interface and the ability to create one more; - configuration-free ethernet interfaces and driver tracking; - queuing and multiple concurrent requests to each ethernet driver; - standard ioctl(2)-based BSD interface management; - radix tree backed, destination-based routing; - routing sockets for standard BSD route reporting and management; - multicast traffic and multicast group membership tracking; - Berkeley Packet Filter (BPF) devices; - standard and custom sysctl(7) nodes for many internals; - a slab allocation based, hybrid static/dynamic memory pool model. Many of its modules come with fairly elaborate comments that cover many aspects of what is going on. The service is primarily a socket driver built on top of the libsockdriver library, but for BPF devices it is at the same time also a character driver. Change-Id: Ib0c02736234b21143915e5fcc0fda8fe408f046f
1719 lines
55 KiB
C
1719 lines
55 KiB
C
/* LWIP service - ethif.c - ethernet interfaces */
|
|
/*
|
|
* The most important aspect of this module is to maintain a send queue for the
|
|
* interface. This send queue consists of packets to send. At times, the user
|
|
* may request a change to the driver configuration. While configuration
|
|
* requests would ideally be enqueued in the send queue, this has proven too
|
|
* problematic to work in practice, especially since out-of-memory conditions
|
|
* may prevent configuration requests from being accepted immediately in such a
|
|
* model. Instead, we take a simple and blunt approach: configuration requests
|
|
* "cut in line" and thus take precedence over pending packets in the send
|
|
* queue. This may not always be entirely correct: for example, packets may be
|
|
* transmitted with the old ethernet address after the network device has
|
|
* already been reconfigured to receive from a new ethernet address. However,
|
|
* this should not be a real problem, and we take care explicitly of perhaps
|
|
* the most problematic case: packets not getting checksummed due to checksum
|
|
* offloading configuration changes.
|
|
*
|
|
* Even with this blunt approach, we maintain three concurrent configurations:
|
|
* the active, the pending, and the wanted configuration. The active one is
|
|
* the last known active configuration at the network driver. It used not only
|
|
* to report whether the device is in RUNNING state, but also to replay the
|
|
* active configuration to a restarted driver. The pending configuration is
|
|
* a partially new configuration that has been given to ndev to send to the
|
|
* driver, but not yet acknowledged by the driver. Finally, the wanted
|
|
* configuration is the latest one that has yet to be given to ndev.
|
|
*
|
|
* Each configuration has a bitmask indicating which part of the configuration
|
|
* has changed, in order to limit work on the driver side. This is also the
|
|
* reason that the pending and wanted configurations are separate: if e.g. a
|
|
* media change is pending at the driver, and the user also requests a mode
|
|
* change, we do not want the media change to be repeated after it has been
|
|
* acknowleged by the driver, just to change the mode as well. In this example
|
|
* the pending configuration will have NDEV_SET_MEDIA set, and the wanted
|
|
* configuration will have NDEV_SET_MODE set. Once acknowledged, the pending
|
|
* bitmask is cleared and the wanted bitmask is tested to see if another
|
|
* configuration change should be given to ndev. Technically, this could lead
|
|
* to starvation of actual packet transmission, but we expect configuration
|
|
* changes to be very rare, since they are always user initiated.
|
|
*
|
|
* It is important to note for understanding the code that for some fields
|
|
* (mode, flags, caps), the three configurations are cascading: even though the
|
|
* wanted configuration may not have NDEV_SET_MODE set, its mode field will
|
|
* still contain the most recently requested mode; that is, the mode in the
|
|
* pending configuration if that one has NDEV_SET_MODE set, or otherwise the
|
|
* mode in the active configuration. For that reason, we carefully merge
|
|
* configuration requests into the next level (wanted -> pending -> active),
|
|
* updating just the fields that have been changed by the previous level. This
|
|
* approach simplifies obtaining current values a lot, but is not very obvious.
|
|
*
|
|
* Also, we never send multiple configuration requests at once, even though
|
|
* ndev would let us do that: we use a single array for the list of multicast
|
|
* ethernet addresses that we send to the driver, which the driver may retrieve
|
|
* (using a memory grant) at any time. We necessarily recompute the multicast
|
|
* list before sending a configuration request, and thus, sending multiple
|
|
* requests at once may lead to the driver retrieving a corrupted list.
|
|
*/
|
|
|
|
#include "lwip.h"
|
|
#include "ethif.h"
|
|
|
|
#include "lwip/etharp.h"
|
|
#include "lwip/ethip6.h"
|
|
#include "lwip/igmp.h"
|
|
#include "lwip/mld6.h"
|
|
|
|
#include <net/if_media.h>
|
|
|
|
#define ETHIF_MAX_MTU 1500 /* maximum MTU value for ethernet */
|
|
#define ETHIF_DEF_MTU ETHIF_MAX_MTU /* default MTU value that we use */
|
|
|
|
#define ETHIF_MCAST_MAX 8 /* maximum number of multicast addresses */
|
|
|
|
struct ethif {
|
|
struct ifdev ethif_ifdev; /* interface device, MUST be first */
|
|
ndev_id_t ethif_ndev; /* network device ID */
|
|
unsigned int ethif_flags; /* interface flags (ETHIFF_) */
|
|
uint32_t ethif_caps; /* driver capabilities (NDEV_CAPS_) */
|
|
uint32_t ethif_media; /* driver-reported media type (IFM_) */
|
|
struct ndev_conf ethif_active; /* active configuration (at driver) */
|
|
struct ndev_conf ethif_pending; /* pending configuration (at ndev) */
|
|
struct ndev_conf ethif_wanted; /* desired configuration (waiting) */
|
|
struct ndev_hwaddr ethif_mclist[ETHIF_MCAST_MAX]; /* multicast list */
|
|
struct { /* send queue (packet/conf refs) */
|
|
struct pbuf *es_head; /* first (oldest) request reference */
|
|
struct pbuf **es_unsentp; /* ptr-ptr to first unsent request */
|
|
struct pbuf **es_tailp; /* ptr-ptr for adding new requests */
|
|
unsigned int es_count; /* buffer count, see ETHIF_PBUF_.. */
|
|
} ethif_snd;
|
|
struct { /* receive queue (packets) */
|
|
struct pbuf *er_head; /* first (oldest) request buffer */
|
|
struct pbuf **er_tailp; /* ptr-ptr for adding new requests */
|
|
} ethif_rcv;
|
|
SIMPLEQ_ENTRY(ethif) ethif_next; /* next in free list */
|
|
} ethif_array[NR_NDEV]; /* any other value would be suboptimal */
|
|
|
|
#define ethif_get_name(ethif) (ifdev_get_name(&(ethif)->ethif_ifdev))
|
|
#define ethif_get_netif(ethif) (ifdev_get_netif(&(ethif)->ethif_ifdev))
|
|
|
|
#define ETHIFF_DISABLED 0x01 /* driver has disappeared */
|
|
#define ETHIFF_FIRST_CONF 0x02 /* first configuration request sent */
|
|
|
|
/*
|
|
* Send queue limit settings. Both are counted in number of pbuf objects.
|
|
* ETHIF_PBUF_MIN is the minimum number of pbuf objects that can always be
|
|
* enqueued on a particular interface's send queue. It should be at least the
|
|
* number of pbufs for one single packet after being reduced to the ndev limit,
|
|
* so NDEV_IOV_MAX (8) is a natural fit. The ETHIF_PBUF_MAX_n values define
|
|
* the maximum number of pbufs that may be used by all interface send queues
|
|
* combined, whichever of the two is smaller. The resulting number must be set
|
|
* fairly high, because at any time there may be a lot of active TCP sockets
|
|
* that all generate a (multi-pbuf) packet as a result of a clock tick. It is
|
|
* currently a function of the size of the buffer pool, capped to a value that
|
|
* is a function of the number of TCP sockets (assuming one packet per socket;
|
|
* up to MSS/BUFSIZE+1 data pbufs, one header pbuf, one extra as margin). The
|
|
* difference between the per-interface guaranteed minimum and the global
|
|
* maximum is what makes up a pool of "spares", which are really just tokens
|
|
* allowing for enqueuing of that many pbufs.
|
|
*/
|
|
#define ETHIF_PBUF_MIN (NDEV_IOV_MAX)
|
|
#define ETHIF_PBUF_MAX_1 (mempool_cur_buffers() >> 1)
|
|
#define ETHIF_PBUF_MAX_2 (NR_TCPSOCK * (TCP_MSS / MEMPOOL_BUFSIZE + 3))
|
|
|
|
static unsigned int ethif_spares;
|
|
|
|
static SIMPLEQ_HEAD(, ethif) ethif_freelist; /* free ethif objects */
|
|
|
|
static const struct ifdev_ops ethif_ops;
|
|
|
|
#ifdef INET6
|
|
static ip6_addr_t ethif_ip6addr_allnodes_ll;
|
|
#endif /* INET6 */
|
|
|
|
/*
|
|
* Initialize the ethernet interfaces module.
|
|
*/
|
|
void
|
|
ethif_init(void)
|
|
{
|
|
unsigned int slot;
|
|
|
|
/* Initialize the list of free ethif objects. */
|
|
SIMPLEQ_INIT(ðif_freelist);
|
|
|
|
for (slot = 0; slot < __arraycount(ethif_array); slot++)
|
|
SIMPLEQ_INSERT_TAIL(ðif_freelist, ðif_array[slot],
|
|
ethif_next);
|
|
|
|
/* Initialize the number of in-use spare tokens. */
|
|
ethif_spares = 0;
|
|
|
|
#ifdef INET6
|
|
/* Preinitialize the link-local all-nodes IPv6 multicast address. */
|
|
ip6_addr_set_allnodes_linklocal(ðif_ip6addr_allnodes_ll);
|
|
#endif /* INET6 */
|
|
}
|
|
|
|
/*
|
|
* As the result of some event, the NetBSD-style interface flags for this
|
|
* interface may have changed. Recompute and update the flags as appropriate.
|
|
*/
|
|
static void
|
|
ethif_update_ifflags(struct ethif * ethif)
|
|
{
|
|
unsigned int ifflags;
|
|
|
|
ifflags = ifdev_get_ifflags(ðif->ethif_ifdev);
|
|
|
|
/* These are the flags that we might update here. */
|
|
ifflags &= ~(IFF_RUNNING | IFF_ALLMULTI);
|
|
|
|
/*
|
|
* For us, the RUNNING flag indicates that -as far as we know- the
|
|
* network device is fully operational and has its I/O engines running.
|
|
* This is a reflection of the current state, not of any intention, so
|
|
* we look at the active configuration here. We use the same approach
|
|
* for one other receive state flags here (ALLMULTI).
|
|
*/
|
|
if ((ethif->ethif_flags &
|
|
(ETHIFF_DISABLED | ETHIFF_FIRST_CONF)) == 0 &&
|
|
ethif->ethif_active.nconf_mode != NDEV_MODE_DOWN) {
|
|
ifflags |= IFF_RUNNING;
|
|
|
|
if (ethif->ethif_active.nconf_mode & NDEV_MODE_MCAST_ALL)
|
|
ifflags |= IFF_ALLMULTI;
|
|
}
|
|
|
|
ifdev_update_ifflags(ðif->ethif_ifdev, ifflags);
|
|
}
|
|
|
|
/*
|
|
* Add a multicast hardware receive address into the set of hardware addresses
|
|
* in the given configuration, if the given address is not already in the
|
|
* configuration's set. Adjust the configuration's mode as needed. Return
|
|
* TRUE If the address was added, and FALSE if the address could not be added
|
|
* due to a full list (of 'max' elements), in which case the mode is changed
|
|
* from receiving from listed multicast addresses to receiving from all
|
|
* multicast addresses.
|
|
*/
|
|
static int
|
|
ethif_add_mcast(struct ndev_conf * nconf, unsigned int max,
|
|
struct ndev_hwaddr * hwaddr)
|
|
{
|
|
unsigned int slot;
|
|
|
|
/*
|
|
* See if the hardware address is already in the list we produced so
|
|
* far. This makes the multicast list generation O(n^2) but we do not
|
|
* expect many entries nor is the list size large anyway.
|
|
*/
|
|
for (slot = 0; slot < nconf->nconf_mccount; slot++)
|
|
if (!memcmp(&nconf->nconf_mclist[slot], hwaddr,
|
|
sizeof(*hwaddr)))
|
|
return TRUE;
|
|
|
|
if (nconf->nconf_mccount < max) {
|
|
memcpy(&nconf->nconf_mclist[slot], hwaddr, sizeof(*hwaddr));
|
|
nconf->nconf_mccount++;
|
|
|
|
nconf->nconf_mode |= NDEV_MODE_MCAST_LIST;
|
|
|
|
return TRUE;
|
|
} else {
|
|
nconf->nconf_mode &= ~NDEV_MODE_MCAST_LIST;
|
|
nconf->nconf_mode |= NDEV_MODE_MCAST_ALL;
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add the ethernet hardware address derived from the given IPv4 multicast
|
|
* address, to the list of multicast addresses.
|
|
*/
|
|
static int
|
|
ethif_add_mcast_v4(struct ndev_conf * nconf, unsigned int max,
|
|
const ip4_addr_t * ip4addr)
|
|
{
|
|
struct ndev_hwaddr hwaddr;
|
|
|
|
/* 01:00:05:xx:xx:xx with the lower 23 bits of the IPv4 address. */
|
|
hwaddr.nhwa_addr[0] = LL_IP4_MULTICAST_ADDR_0;
|
|
hwaddr.nhwa_addr[1] = LL_IP4_MULTICAST_ADDR_1;
|
|
hwaddr.nhwa_addr[2] = LL_IP4_MULTICAST_ADDR_2;
|
|
hwaddr.nhwa_addr[3] = (ip4_addr_get_u32(ip4addr) >> 16) & 0x7f;
|
|
hwaddr.nhwa_addr[4] = (ip4_addr_get_u32(ip4addr) >> 8) & 0xff;
|
|
hwaddr.nhwa_addr[5] = (ip4_addr_get_u32(ip4addr) >> 0) & 0xff;
|
|
|
|
return ethif_add_mcast(nconf, max, &hwaddr);
|
|
}
|
|
|
|
/*
|
|
* Add the ethernet hardware address derived from the given IPv6 multicast
|
|
* address, to the list of multicast addresses.
|
|
*/
|
|
static int
|
|
ethif_add_mcast_v6(struct ndev_conf * nconf, unsigned int max,
|
|
const ip6_addr_t * ip6addr)
|
|
{
|
|
struct ndev_hwaddr hwaddr;
|
|
|
|
/* 33:33:xx:xx:xx:xx with the lower 32 bits of the IPv6 address. */
|
|
hwaddr.nhwa_addr[0] = LL_IP6_MULTICAST_ADDR_0;
|
|
hwaddr.nhwa_addr[1] = LL_IP6_MULTICAST_ADDR_1;
|
|
memcpy(&hwaddr.nhwa_addr[2], &ip6addr->addr[3], sizeof(uint32_t));
|
|
|
|
return ethif_add_mcast(nconf, max, &hwaddr);
|
|
}
|
|
|
|
/*
|
|
* Set up the multicast mode for a configuration that is to be sent to a
|
|
* network driver, generating a multicast receive address list for the driver
|
|
* as applicable.
|
|
*/
|
|
static void
|
|
ethif_gen_mcast(struct ethif * ethif, struct ndev_conf * nconf)
|
|
{
|
|
struct igmp_group *group4;
|
|
struct mld_group *group6;
|
|
unsigned int max;
|
|
|
|
/* Make sure that multicast is supported at all for this interface. */
|
|
if (!(ethif->ethif_caps & NDEV_CAP_MCAST))
|
|
return;
|
|
|
|
/* Make sure the mode is being (re)configured to be up. */
|
|
if (!(nconf->nconf_set & NDEV_SET_MODE) ||
|
|
nconf->nconf_mode == NDEV_MODE_DOWN)
|
|
return;
|
|
|
|
/* Recompute the desired multicast flags. */
|
|
nconf->nconf_mode &= ~(NDEV_MODE_MCAST_LIST | NDEV_MODE_MCAST_ALL);
|
|
|
|
/* If promiscuous mode is enabled, receive all multicast packets. */
|
|
if (nconf->nconf_mode & NDEV_MODE_PROMISC) {
|
|
nconf->nconf_mode |= NDEV_MODE_MCAST_ALL;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Map all IGMP/MLD6 multicast addresses to ethernet addresses, merging
|
|
* any duplicates to save slots. We have to add the MLD6 all-nodes
|
|
* multicast address ourselves, which also means the list is never
|
|
* empty unless compiling with USE_INET6=no. If the list is too small
|
|
* for all addresses, opt to receive all multicast packets instead.
|
|
*/
|
|
nconf->nconf_mclist = ethif->ethif_mclist;
|
|
nconf->nconf_mccount = 0;
|
|
max = __arraycount(ethif->ethif_mclist);
|
|
|
|
for (group4 = netif_igmp_data(ethif_get_netif(ethif)); group4 != NULL;
|
|
group4 = group4->next)
|
|
if (!ethif_add_mcast_v4(nconf, max, &group4->group_address))
|
|
return;
|
|
|
|
#ifdef INET6
|
|
if (!ethif_add_mcast_v6(nconf, max, ðif_ip6addr_allnodes_ll))
|
|
return;
|
|
#endif /* INET6 */
|
|
|
|
for (group6 = netif_mld6_data(ethif_get_netif(ethif)); group6 != NULL;
|
|
group6 = group6->next)
|
|
if (!ethif_add_mcast_v6(nconf, max, &group6->group_address))
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Merge a source configuration into a destination configuration, copying any
|
|
* fields intended to be set from the source into the destination and clearing
|
|
* the "set" mask in the source, without changing the source fields, so that
|
|
* the source will reflect the destination's contents.
|
|
*/
|
|
static void
|
|
ethif_merge_conf(struct ndev_conf * dconf, struct ndev_conf * sconf)
|
|
{
|
|
|
|
dconf->nconf_set |= sconf->nconf_set;
|
|
|
|
if (sconf->nconf_set & NDEV_SET_MODE)
|
|
dconf->nconf_mode = sconf->nconf_mode;
|
|
if (sconf->nconf_set & NDEV_SET_CAPS)
|
|
dconf->nconf_caps = sconf->nconf_caps;
|
|
if (sconf->nconf_set & NDEV_SET_FLAGS)
|
|
dconf->nconf_flags = sconf->nconf_flags;
|
|
if (sconf->nconf_set & NDEV_SET_MEDIA)
|
|
dconf->nconf_media = sconf->nconf_media;
|
|
if (sconf->nconf_set & NDEV_SET_HWADDR)
|
|
memcpy(&dconf->nconf_hwaddr, &sconf->nconf_hwaddr,
|
|
sizeof(dconf->nconf_hwaddr));
|
|
|
|
sconf->nconf_set = 0;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if we can and should try to pass a configuration request to the
|
|
* ndev layer on this interface, or FALSE otherwise.
|
|
*/
|
|
static int
|
|
ethif_can_conf(struct ethif * ethif)
|
|
{
|
|
|
|
/* Is there a configuration change waiting? The common case is no. */
|
|
if (ethif->ethif_wanted.nconf_set == 0)
|
|
return FALSE;
|
|
|
|
/*
|
|
* Is there a configuration change pending already? Then wait for it
|
|
* to be acknowledged first.
|
|
*/
|
|
if (ethif->ethif_pending.nconf_set != 0)
|
|
return FALSE;
|
|
|
|
/* Make sure the interface is in the appropriate state. */
|
|
if (ethif->ethif_flags & ETHIFF_DISABLED)
|
|
return FALSE;
|
|
|
|
/* First let all current packet send requests finish. */
|
|
return (ethif->ethif_snd.es_unsentp == ðif->ethif_snd.es_head);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if we can and should try to pass the next unsent packet send
|
|
* request to the ndev layer on this interface, or FALSE otherwise.
|
|
*/
|
|
static int
|
|
ethif_can_send(struct ethif * ethif)
|
|
{
|
|
|
|
/* Is there anything to hand to ndev at all? The common case is no. */
|
|
if (*ethif->ethif_snd.es_unsentp == NULL)
|
|
return FALSE;
|
|
|
|
/*
|
|
* Is there a configuration change pending? Then we cannot send
|
|
* packets yet. Always let all configuration changes through first.
|
|
*/
|
|
if (ethif->ethif_pending.nconf_set != 0 ||
|
|
ethif->ethif_wanted.nconf_set != 0)
|
|
return FALSE;
|
|
|
|
/* Make sure the interface is in the appropriate state. */
|
|
if ((ethif->ethif_flags & (ETHIFF_DISABLED | ETHIFF_FIRST_CONF)) != 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if we can and should try to receive packets on this interface
|
|
* and are ready to accept received packets, or FALSE otherwise.
|
|
*/
|
|
static int
|
|
ethif_can_recv(struct ethif * ethif)
|
|
{
|
|
|
|
if ((ethif->ethif_flags & (ETHIFF_DISABLED | ETHIFF_FIRST_CONF)) != 0)
|
|
return FALSE;
|
|
|
|
/*
|
|
* We do not check the link status here. There is no reason not to
|
|
* spawn receive requests, or accept received packets, while the link
|
|
* is reported to be down.
|
|
*/
|
|
return ifdev_is_up(ðif->ethif_ifdev);
|
|
}
|
|
|
|
/*
|
|
* Polling function, invoked after each message loop iteration. Check whether
|
|
* any configuration change or packets can be sent to the driver, and whether
|
|
* any new packet receive requests can be enqueued at the driver.
|
|
*/
|
|
static void
|
|
ethif_poll(struct ifdev * ifdev)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
struct pbuf *pbuf, *pref;
|
|
|
|
/*
|
|
* If a configuration request is desired, see if we can send it to the
|
|
* driver now. Otherwise, attempt to send any packets if possible.
|
|
* In both cases, a failure of the ndev call indicates that we should
|
|
* try again later.
|
|
*/
|
|
if (ethif_can_conf(ethif)) {
|
|
ethif_gen_mcast(ethif, ðif->ethif_wanted);
|
|
|
|
/*
|
|
* On success, move the wanted configuration into the pending
|
|
* slot. Otherwise, try again on the next poll iteration.
|
|
*/
|
|
if (ndev_conf(ethif->ethif_ndev, ðif->ethif_wanted) == OK)
|
|
ethif_merge_conf(ðif->ethif_pending,
|
|
ðif->ethif_wanted);
|
|
} else {
|
|
while (ethif_can_send(ethif)) {
|
|
pref = *ethif->ethif_snd.es_unsentp;
|
|
|
|
if (pref->type == PBUF_REF)
|
|
pbuf = (struct pbuf *)pref->payload;
|
|
else
|
|
pbuf = pref;
|
|
|
|
if (ndev_send(ethif->ethif_ndev, pbuf) == OK)
|
|
ethif->ethif_snd.es_unsentp =
|
|
pchain_end(pref);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Attempt to create additional receive requests for the driver, if
|
|
* applicable. We currently do not set a limit on the maximum number
|
|
* of concurrently pending receive requests here, because the maximum
|
|
* in ndev is already quite low. That may have to be changed one day.
|
|
*/
|
|
while (ethif_can_recv(ethif) && ndev_can_recv(ethif->ethif_ndev)) {
|
|
/*
|
|
* Allocate a buffer for the network device driver to copy the
|
|
* received packet into. Allocation may fail if no buffers are
|
|
* available at this time; in that case simply try again later.
|
|
* We add room for a VLAN tag even though we do not support
|
|
* such tags just yet.
|
|
*/
|
|
if ((pbuf = pchain_alloc(PBUF_RAW, ETH_PAD_LEN + ETH_HDR_LEN +
|
|
ETHIF_MAX_MTU + NDEV_ETH_PACKET_TAG)) == NULL)
|
|
break;
|
|
|
|
/*
|
|
* Effectively throw away two bytes in order to align TCP/IP
|
|
* header fields to 32 bits. See the short discussion in
|
|
* lwipopts.h as to why we are not using lwIP's ETH_PAD_SIZE.
|
|
*/
|
|
util_pbuf_header(pbuf, -ETH_PAD_LEN);
|
|
|
|
/*
|
|
* Send the request to the driver. This may still fail due to
|
|
* grant allocation failure, in which case we try again later.
|
|
*/
|
|
if (ndev_recv(ethif->ethif_ndev, pbuf) != OK) {
|
|
pbuf_free(pbuf);
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Hold on to the packet buffer until the receive request
|
|
* completes or is aborted, or the driver disappears.
|
|
*/
|
|
*ethif->ethif_rcv.er_tailp = pbuf;
|
|
ethif->ethif_rcv.er_tailp = pchain_end(pbuf);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Complete the link-layer header of the packet by filling in a source address.
|
|
* This is relevant for BPF-generated packets only, and thus we can safely
|
|
* modify the given pbuf.
|
|
*/
|
|
static void
|
|
ethif_hdrcmplt(struct ifdev * ifdev, struct pbuf * pbuf)
|
|
{
|
|
struct netif *netif;
|
|
|
|
/* Make sure there is an ethernet packet header at all. */
|
|
if (pbuf->len < ETH_HDR_LEN)
|
|
return;
|
|
|
|
netif = ifdev_get_netif(ifdev);
|
|
|
|
/*
|
|
* Insert the source ethernet address into the packet. The source
|
|
* address is located right after the destination address at the start
|
|
* of the packet.
|
|
*/
|
|
memcpy((uint8_t *)pbuf->payload + netif->hwaddr_len, netif->hwaddr,
|
|
netif->hwaddr_len);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if the given additional number of spare tokens may be used, or
|
|
* FALSE if the limit has been reached. Each spare token represents one
|
|
* enqueued pbuf. The limit must be such that we do not impede normal traffic
|
|
* but also do not spend the entire buffer pool on enqueued packets.
|
|
*/
|
|
static int
|
|
ethif_can_spare(unsigned int spares)
|
|
{
|
|
unsigned int max;
|
|
|
|
/*
|
|
* Use the configured maximum, which depends on the current size of the
|
|
* buffer pool.
|
|
*/
|
|
max = ETHIF_PBUF_MAX_1;
|
|
|
|
/*
|
|
* However, limit the total to a value based on the maximum number of
|
|
* TCP packets that can, in the worst case, be expected to queue up at
|
|
* any single moment.
|
|
*/
|
|
if (max > ETHIF_PBUF_MAX_2)
|
|
max = ETHIF_PBUF_MAX_2;
|
|
|
|
return (spares + ethif_spares <= max - ETHIF_PBUF_MIN * NR_NDEV);
|
|
}
|
|
|
|
/*
|
|
* Process a packet as output on an ethernet interface.
|
|
*/
|
|
static err_t
|
|
ethif_output(struct ifdev * ifdev, struct pbuf * pbuf, struct netif * netif)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
struct pbuf *pref, *pcopy;
|
|
size_t padding;
|
|
unsigned int count, spares;
|
|
|
|
/* Packets must never be sent on behalf of another interface. */
|
|
assert(netif == NULL);
|
|
|
|
/*
|
|
* The caller already rejects packets while the interface or link is
|
|
* down. We do want to keep enqueuing packets while the driver is
|
|
* restarting, so do not check ETHIFF_DISABLED or ETHIFF_FIRST_CONF.
|
|
*/
|
|
|
|
/*
|
|
* Reject oversized packets immediately. This should not happen.
|
|
* Undersized packets are padded below.
|
|
*/
|
|
if (pbuf->tot_len > NDEV_ETH_PACKET_MAX) {
|
|
printf("LWIP: attempt to send oversized ethernet packet "
|
|
"(size %u)\n", pbuf->tot_len);
|
|
util_stacktrace();
|
|
|
|
return ERR_MEM;
|
|
}
|
|
|
|
/*
|
|
* The original lwIP idea for processing output packets is that we make
|
|
* a copy of the packet here, so that lwIP is free to do whatever it
|
|
* wants with the original packet (e.g., keep on the TCP retransmission
|
|
* queue). More recently, lwIP has made progress towards allowing the
|
|
* packet to be referenced only, decreasing the reference count only
|
|
* once the packet has been actually sent. For many embedded systems,
|
|
* that change now allows zero-copy transmission with direct DMA from
|
|
* the provided packet buffer. We are not so lucky: we have to make an
|
|
* additional inter-process copy anyway. We do however use the same
|
|
* referencing system to avoid having to make yet another copy of the
|
|
* packet here.
|
|
*
|
|
* There was previously a check on (pbuf->ref > 1) here, to ensure that
|
|
* we would never enqueue packets that are retransmitted while we were
|
|
* still in the process of sending the initial copy. Now that for ARP
|
|
* and NDP queuing, packets are referenced rather than copied (lwIP
|
|
* patch #9272), we can no longer perform that check: packets may
|
|
* legitimately have a reference count of 2 at this point. The second
|
|
* reference will be dropped by the caller immediately after we return.
|
|
*/
|
|
|
|
/*
|
|
* There are two cases in which we need to make a copy of the packet
|
|
* after all:
|
|
*
|
|
* 1) in the case that the packet needs to be padded in order to reach
|
|
* the minimum ethernet packet size (for drivers' convenience);
|
|
* 2) in the (much more exceptional) case that the given pbuf chain
|
|
* exceeds the maximum vector size for network driver requests.
|
|
*/
|
|
if (NDEV_ETH_PACKET_MIN > pbuf->tot_len)
|
|
padding = NDEV_ETH_PACKET_MIN - pbuf->tot_len;
|
|
else
|
|
padding = 0;
|
|
|
|
count = pbuf_clen(pbuf);
|
|
|
|
if (padding != 0 || count > NDEV_IOV_MAX) {
|
|
pcopy = pchain_alloc(PBUF_RAW, pbuf->tot_len + padding);
|
|
if (pcopy == NULL) {
|
|
ifdev_output_drop(ifdev);
|
|
|
|
return ERR_MEM;
|
|
}
|
|
|
|
if (pbuf_copy(pcopy, pbuf) != ERR_OK)
|
|
panic("unexpected pbuf copy failure");
|
|
|
|
if (padding > 0) {
|
|
/*
|
|
* This restriction can be lifted if needed, but it
|
|
* involves hairy pbuf traversal and our standard pool
|
|
* size should be way in excess of the minimum packet
|
|
* size.
|
|
*/
|
|
assert(pcopy->len == pbuf->tot_len + padding);
|
|
|
|
memset((char *)pcopy->payload + pbuf->tot_len, 0,
|
|
padding);
|
|
}
|
|
|
|
count = pbuf_clen(pcopy);
|
|
assert(count <= NDEV_IOV_MAX);
|
|
|
|
pbuf = pcopy;
|
|
} else
|
|
pcopy = NULL;
|
|
|
|
/*
|
|
* Restrict the size of the send queue, so that it will not exhaust the
|
|
* buffer pool.
|
|
*/
|
|
if (ethif->ethif_snd.es_count >= ETHIF_PBUF_MIN)
|
|
spares = count;
|
|
else if (ethif->ethif_snd.es_count + count > ETHIF_PBUF_MIN)
|
|
spares = ethif->ethif_snd.es_count + count - ETHIF_PBUF_MIN;
|
|
else
|
|
spares = 0;
|
|
|
|
if (spares > 0 && !ethif_can_spare(spares)) {
|
|
if (pcopy != NULL)
|
|
pbuf_free(pcopy);
|
|
|
|
ifdev_output_drop(ifdev);
|
|
|
|
return ERR_MEM;
|
|
}
|
|
|
|
/*
|
|
* A side effect of the referencing approach is that we cannot touch
|
|
* the last pbuf's "next" pointer. Thus, we need another way of
|
|
* linking together the buffers on the send queue. We use a linked
|
|
* list of PBUF_REF-type buffers for this instead. However, do this
|
|
* only when we have not made a copy of the original pbuf, because then
|
|
* we might as well use the copy instead.
|
|
*/
|
|
if (pcopy == NULL) {
|
|
if ((pref = pbuf_alloc(PBUF_RAW, 0, PBUF_REF)) == NULL) {
|
|
ifdev_output_drop(ifdev);
|
|
|
|
return ERR_MEM;
|
|
}
|
|
|
|
pbuf_ref(pbuf);
|
|
|
|
pref->payload = pbuf;
|
|
pref->tot_len = 0;
|
|
pref->len = count;
|
|
} else
|
|
pref = pcopy;
|
|
|
|
/* If the send queue was empty so far, set the IFF_OACTIVE flag. */
|
|
if (ethif->ethif_snd.es_head == NULL)
|
|
ifdev_update_ifflags(ðif->ethif_ifdev,
|
|
ifdev_get_ifflags(ðif->ethif_ifdev) | IFF_OACTIVE);
|
|
|
|
/*
|
|
* Enqueue the packet on the send queue. It will be sent from the
|
|
* polling function as soon as possible. TODO: see if sending it from
|
|
* here makes any performance difference at all.
|
|
*/
|
|
*ethif->ethif_snd.es_tailp = pref;
|
|
ethif->ethif_snd.es_tailp = pchain_end(pref);
|
|
|
|
ethif->ethif_snd.es_count += count;
|
|
ethif_spares += spares;
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
/*
|
|
* Transmit an ethernet packet on an ethernet interface, as requested by lwIP.
|
|
*/
|
|
static err_t
|
|
ethif_linkoutput(struct netif * netif, struct pbuf * pbuf)
|
|
{
|
|
struct ifdev *ifdev = netif_get_ifdev(netif);
|
|
|
|
/*
|
|
* Let ifdev make the callback to our output function, so that it can
|
|
* pass the packet to BPF devices and generically update statistics.
|
|
*/
|
|
return ifdev_output(ifdev, pbuf, NULL /*netif*/, TRUE /*to_bpf*/,
|
|
TRUE /*hdrcmplt*/);
|
|
}
|
|
|
|
/*
|
|
* The multicast address list has changed. See to it that the change will make
|
|
* it to the network driver at some point.
|
|
*/
|
|
static err_t
|
|
ethif_set_mcast(struct ethif * ethif)
|
|
{
|
|
|
|
/*
|
|
* Simply generate a mode change request, unless the interface is down.
|
|
* Once the mode change request is about to be sent to the driver, we
|
|
* will recompute the multicast settings.
|
|
*/
|
|
if (ifdev_is_up(ðif->ethif_ifdev))
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_MODE;
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
/*
|
|
* An IPv4 multicast address has been added to or removed from the list of IPv4
|
|
* multicast addresses.
|
|
*/
|
|
static err_t
|
|
ethif_set_mcast_v4(struct netif * netif, const ip4_addr_t * group __unused,
|
|
enum netif_mac_filter_action action __unused)
|
|
{
|
|
|
|
return ethif_set_mcast((struct ethif *)netif_get_ifdev(netif));
|
|
}
|
|
|
|
/*
|
|
* An IPv6 multicast address has been added to or removed from the list of IPv6
|
|
* multicast addresses.
|
|
*/
|
|
static err_t
|
|
ethif_set_mcast_v6(struct netif * netif, const ip6_addr_t * group __unused,
|
|
enum netif_mac_filter_action action __unused)
|
|
{
|
|
|
|
return ethif_set_mcast((struct ethif *)netif_get_ifdev(netif));
|
|
}
|
|
|
|
/*
|
|
* Initialization function for an ethernet-type netif interface, called from
|
|
* lwIP at interface creation time.
|
|
*/
|
|
static err_t
|
|
ethif_init_netif(struct ifdev * ifdev, struct netif * netif)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
/*
|
|
* Fill in a dummy name. Since it is only two characters, do not
|
|
* bother trying to reuse part of the given name. If this name is ever
|
|
* actually used anywhere, the dummy should suffice for debugging.
|
|
*/
|
|
netif->name[0] = 'e';
|
|
netif->name[1] = 'n';
|
|
|
|
netif->linkoutput = ethif_linkoutput;
|
|
|
|
memset(netif->hwaddr, 0, sizeof(netif->hwaddr));
|
|
|
|
/*
|
|
* Set the netif flags, partially based on the capabilities reported by
|
|
* the network device driver. The reason that we do this now is that
|
|
* lwIP tests for some of these flags and starts appropriate submodules
|
|
* (e.g., IGMP) right after returning from this function. If we set
|
|
* the flags later, we also have to take over management of those
|
|
* submodules, which is something we'd rather avoid. For this reason
|
|
* in particular, we also do not support capability mask changes after
|
|
* driver restarts - see ethif_enable().
|
|
*/
|
|
netif->flags = NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;
|
|
|
|
if (ethif->ethif_caps & NDEV_CAP_BCAST)
|
|
netif->flags |= NETIF_FLAG_BROADCAST;
|
|
|
|
if (ethif->ethif_caps & NDEV_CAP_MCAST) {
|
|
/* The IGMP code adds the all-stations multicast entry. */
|
|
netif->igmp_mac_filter = ethif_set_mcast_v4;
|
|
|
|
netif->flags |= NETIF_FLAG_IGMP;
|
|
|
|
/* For MLD6 we have to add the all-nodes entry ourselves. */
|
|
netif->mld_mac_filter = ethif_set_mcast_v6;
|
|
|
|
netif->flags |= NETIF_FLAG_MLD6;
|
|
}
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that a new network device driver has appeared, with
|
|
* the given ndev identifier, a driver-given name, and a certain set of
|
|
* capabilities. Create a new ethernet interface object for it. On success,
|
|
* return a pointer to the object (for later callbacks from ndev). In that
|
|
* case, the ndev layer will always immediately call ethif_enable() afterwards.
|
|
* On failure, return NULL, in which case ndev will forget about the driver.
|
|
*/
|
|
struct ethif *
|
|
ethif_add(ndev_id_t id, const char * name, uint32_t caps)
|
|
{
|
|
struct ethif *ethif;
|
|
unsigned int ifflags;
|
|
int r;
|
|
|
|
/*
|
|
* First make sure that the interface name is valid, unique, and not
|
|
* reserved for virtual interface types.
|
|
*/
|
|
if ((r = ifdev_check_name(name, NULL /*vtype_slot*/)) != OK) {
|
|
/*
|
|
* There is some risk in printing bad stuff, but this may help
|
|
* in preventing serious driver writer frustration..
|
|
*/
|
|
printf("LWIP: invalid driver name '%s' (%d)\n", name, r);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Then see if there is a free ethernet interface object available. */
|
|
if (SIMPLEQ_EMPTY(ðif_freelist)) {
|
|
printf("LWIP: out of slots for driver name '%s'\n", name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* All good; set up the interface. First initialize the object, since
|
|
* adding the interface to lwIP might spawn some activity right away.
|
|
*/
|
|
ethif = SIMPLEQ_FIRST(ðif_freelist);
|
|
SIMPLEQ_REMOVE_HEAD(ðif_freelist, ethif_next);
|
|
|
|
/* Initialize the ethif structure. */
|
|
memset(ethif, 0, sizeof(*ethif));
|
|
ethif->ethif_ndev = id;
|
|
ethif->ethif_flags = ETHIFF_DISABLED;
|
|
ethif->ethif_caps = caps;
|
|
|
|
ethif->ethif_snd.es_head = NULL;
|
|
ethif->ethif_snd.es_unsentp = ðif->ethif_snd.es_head;
|
|
ethif->ethif_snd.es_tailp = ðif->ethif_snd.es_head;
|
|
ethif->ethif_snd.es_count = 0;
|
|
|
|
ethif->ethif_rcv.er_head = NULL;
|
|
ethif->ethif_rcv.er_tailp = ðif->ethif_rcv.er_head;
|
|
|
|
/*
|
|
* Set all the three configurations to the same initial values. Since
|
|
* any change to the configuration will go through all three, this
|
|
* allows us to obtain various parts of the status (in particular, the
|
|
* mode, flags, enabled capabilities, and media type selection) from
|
|
* any of the three without having to consult the others. Note that
|
|
* the hardware address is set to a indeterminate initial value, as it
|
|
* is left to the network driver unless specifically overridden.
|
|
*/
|
|
ethif->ethif_active.nconf_set = 0;
|
|
ethif->ethif_active.nconf_mode = NDEV_MODE_DOWN;
|
|
ethif->ethif_active.nconf_flags = 0;
|
|
ethif->ethif_active.nconf_caps = 0;
|
|
ethif->ethif_active.nconf_media =
|
|
IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, 0);
|
|
memcpy(ðif->ethif_pending, ðif->ethif_active,
|
|
sizeof(ethif->ethif_pending));
|
|
memcpy(ðif->ethif_wanted, ðif->ethif_pending,
|
|
sizeof(ethif->ethif_wanted));
|
|
|
|
/*
|
|
* Compute the initial NetBSD-style interface flags. The IFF_SIMPLEX
|
|
* interface flag is always enabled because we do not support network
|
|
* drivers that are receiving their own packets. In particular, lwIP
|
|
* currently does not deal well with receiving back its own multicast
|
|
* packets, which leads to IPv6 DAD failures. The other two flags
|
|
* (IFF_BROADCAST, IFF_MULTICAST) denote capabilities, not enabled
|
|
* receipt modes.
|
|
*/
|
|
ifflags = IFF_SIMPLEX;
|
|
if (caps & NDEV_CAP_BCAST)
|
|
ifflags |= IFF_BROADCAST;
|
|
if (caps & NDEV_CAP_MCAST)
|
|
ifflags |= IFF_MULTICAST;
|
|
|
|
/* Finally, add the interface to ifdev and lwIP. This cannot fail. */
|
|
ifdev_add(ðif->ethif_ifdev, name, ifflags, IFT_ETHER, ETH_HDR_LEN,
|
|
ETHARP_HWADDR_LEN, DLT_EN10MB, ETHIF_DEF_MTU,
|
|
ND6_IFF_PERFORMNUD | ND6_IFF_AUTO_LINKLOCAL, ðif_ops);
|
|
|
|
return ethif;
|
|
}
|
|
|
|
/*
|
|
* The link status and/or media type of an ethernet interface has changed.
|
|
*/
|
|
static void
|
|
ethif_set_status(struct ethif * ethif, uint32_t link, uint32_t media)
|
|
{
|
|
unsigned int iflink;
|
|
|
|
/* We save the media type locally for now. */
|
|
ethif->ethif_media = media;
|
|
|
|
/* Let the ifdev module handle the details of the link change. */
|
|
switch (link) {
|
|
case NDEV_LINK_UP: iflink = LINK_STATE_UP; break;
|
|
case NDEV_LINK_DOWN: iflink = LINK_STATE_DOWN; break;
|
|
default: iflink = LINK_STATE_UNKNOWN; break;
|
|
}
|
|
|
|
ifdev_update_link(ðif->ethif_ifdev, iflink);
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that a previously added or disabled network device
|
|
* driver has been (re)enabled. Start by initializing the driver. Return TRUE
|
|
* if the interface could indeed be enabled, or FALSE if it should be forgotten
|
|
* altogether after all.
|
|
*/
|
|
int
|
|
ethif_enable(struct ethif * ethif, const char * name,
|
|
const struct ndev_hwaddr * hwaddr, uint8_t hwaddr_len, uint32_t caps,
|
|
uint32_t link, uint32_t media)
|
|
{
|
|
int r;
|
|
|
|
assert(ethif->ethif_flags & ETHIFF_DISABLED);
|
|
|
|
/*
|
|
* One disadvantage of keeping service labels and ethernet driver names
|
|
* disjunct is that the ethernet driver may mess with its name between
|
|
* restarts. Ultimately we may end up renaming our ethernet drivers
|
|
* such that their labels match their names, in which case we no longer
|
|
* need the drivers themselves to produce a name, and we can retire
|
|
* this check.
|
|
*/
|
|
if (name != NULL && strcmp(ethif_get_name(ethif), name)) {
|
|
printf("LWIP: driver '%s' restarted with name '%s'\n",
|
|
ethif_get_name(ethif), name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* The hardware address length is just a sanity check for now. After
|
|
* the initialization reply, we assume the same length is used for all
|
|
* addresses, which is also the maximum, namely 48 bits (six bytes).
|
|
*/
|
|
if (hwaddr_len != ETHARP_HWADDR_LEN) {
|
|
printf("LWIP: driver '%s' reports hwaddr length %u\n",
|
|
ethif_get_name(ethif), hwaddr_len);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If the driver has changed its available capabilities as a result of
|
|
* a restart, we have a problem: we may already have configured the
|
|
* interface's netif object to make use of of some of those
|
|
* capabilities. TODO: we can deal with some cases (e.g., disappearing
|
|
* checksum offloading capabilities) with some effort, and with other
|
|
* cases (e.g., disappearing multicast support) with a LOT more effort.
|
|
*/
|
|
if (ethif->ethif_caps != caps) {
|
|
printf("LWIP: driver '%s' changed capabilities\n",
|
|
ethif_get_name(ethif));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Set the hardware address on the interface, unless a request is
|
|
* currently pending to change it, in which case the new address has
|
|
* been set already and we do not want to revert that change. If not,
|
|
* we always set the address, because it may have changed as part of a
|
|
* driver restart and we do not want to get out of sync with it, nor
|
|
* can we necessarily change it back.
|
|
*/
|
|
if (!(ethif->ethif_active.nconf_set & NDEV_SET_HWADDR) &&
|
|
!(ethif->ethif_pending.nconf_set & NDEV_SET_HWADDR))
|
|
ifdev_update_hwaddr(ðif->ethif_ifdev, hwaddr->nhwa_addr,
|
|
(name == NULL) /*is_factory*/);
|
|
|
|
/*
|
|
* At this point, only one more thing can fail: it is possible that we
|
|
* do not manage to send the first configuration request due to memory
|
|
* shortage. This is extremely unlikely to happen, so send the conf
|
|
* request first and forget the entire driver if it fails.
|
|
*/
|
|
/*
|
|
* Always generate a new multicast list before sending a configuration
|
|
* request, and at no other time (since there may be a grant for it).
|
|
*/
|
|
ethif_gen_mcast(ethif, ðif->ethif_active);
|
|
|
|
if ((r = ndev_conf(ethif->ethif_ndev, ðif->ethif_active)) != OK) {
|
|
printf("LWIP: sending first configuration to '%s' failed "
|
|
"(%d)\n", ethif_get_name(ethif), r);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
ethif_set_status(ethif, link, media);
|
|
|
|
ethif->ethif_flags &= ~ETHIFF_DISABLED;
|
|
ethif->ethif_flags |= ETHIFF_FIRST_CONF;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* The configuration change stored in the "pending" slot of the given ethif
|
|
* object has been acknowledged by the network device driver (or the driver has
|
|
* died, see ethif_disable()). Apply changes to the "active" slot of the given
|
|
* ethif object, as well as previously delayed changes to lwIP through netif.
|
|
*/
|
|
static void
|
|
ethif_post_conf(struct ethif * ethif)
|
|
{
|
|
struct ndev_conf *nconf;
|
|
unsigned int flags;
|
|
|
|
nconf = ðif->ethif_pending;
|
|
|
|
/*
|
|
* Now that the driver configuration has changed, we know that the
|
|
* new checksum settings will be applied to all sent and received
|
|
* packets, and we can disable checksumming flags in netif as desired.
|
|
* Enabling checksumming flags has already been done earlier on.
|
|
*/
|
|
if (nconf->nconf_set & NDEV_SET_CAPS) {
|
|
flags = ethif_get_netif(ethif)->chksum_flags;
|
|
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_IP4_TX)
|
|
flags &= ~NETIF_CHECKSUM_GEN_IP;
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_IP4_RX)
|
|
flags &= ~NETIF_CHECKSUM_CHECK_IP;
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_UDP_TX)
|
|
flags &= ~NETIF_CHECKSUM_GEN_UDP;
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_UDP_RX)
|
|
flags &= ~NETIF_CHECKSUM_CHECK_UDP;
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_TCP_TX)
|
|
flags &= ~NETIF_CHECKSUM_GEN_TCP;
|
|
if (nconf->nconf_caps & NDEV_CAP_CS_TCP_RX)
|
|
flags &= ~NETIF_CHECKSUM_CHECK_TCP;
|
|
|
|
NETIF_SET_CHECKSUM_CTRL(ethif_get_netif(ethif), flags);
|
|
}
|
|
|
|
/*
|
|
* Merge any individual parts of the now acknowledged configuration
|
|
* changes into the active configuration. The result is that we are
|
|
* able to reapply these changes at any time should the network driver
|
|
* be restarted. In addition, by only setting bits for fields that
|
|
* have actually changed, we can later tell whether the user wanted the
|
|
* change or ethif should just take over what the driver reports after
|
|
* a restart; this is important for HW-address and media settings.
|
|
*/
|
|
ethif_merge_conf(ðif->ethif_active, ðif->ethif_pending);
|
|
}
|
|
|
|
/*
|
|
* All receive requests have been canceled at the ndev layer, because the
|
|
* network device driver has been restarted or shut down. Clear the receive
|
|
* queue, freeing any packets in it.
|
|
*/
|
|
static void
|
|
ethif_drain(struct ethif * ethif)
|
|
{
|
|
struct pbuf *pbuf, **pnext;
|
|
|
|
while ((pbuf = ethif->ethif_rcv.er_head) != NULL) {
|
|
pnext = pchain_end(pbuf);
|
|
|
|
if ((ethif->ethif_rcv.er_head = *pnext) == NULL)
|
|
ethif->ethif_rcv.er_tailp = ðif->ethif_rcv.er_head;
|
|
|
|
*pnext = NULL;
|
|
pbuf_free(pbuf);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The network device driver has stopped working (i.e., crashed), but has not
|
|
* been shut down completely, and is expect to come back later.
|
|
*/
|
|
void
|
|
ethif_disable(struct ethif * ethif)
|
|
{
|
|
|
|
/*
|
|
* We assume, optimistically, that a new instance of the driver will be
|
|
* brought up soon after which we can continue operating as before. As
|
|
* such, we do not want to change most of the user-visible state until
|
|
* we know for sure that our optimism was in vain. In particular, we
|
|
* do *not* want to change the following parts of the state here:
|
|
*
|
|
* - the contents of the send queue;
|
|
* - the state of the interface (up or down);
|
|
* - the state and media type of the physical link.
|
|
*
|
|
* The main user-visible indication of the crash will be that the
|
|
* interface does not have the IFF_RUNNING flag set.
|
|
*/
|
|
|
|
/*
|
|
* If a configuration request was pending, it will be lost now. Highly
|
|
* unintuitively, make the requested configuration the *active* one,
|
|
* just as though the request completed successfully. This works,
|
|
* because once the driver comes back, the active configuration will be
|
|
* replayed as initial configuration. Therefore, by pretending that
|
|
* the current request went through, we ensure that it too will be sent
|
|
* to the new instance--before anything else is allowed to happen.
|
|
*/
|
|
if (ethif->ethif_pending.nconf_set != 0)
|
|
ethif_post_conf(ethif);
|
|
|
|
/*
|
|
* Any packet send requests have been lost, too, and likewise forgotten
|
|
* by ndev. Thus, we need to forget that we sent any packets, so that
|
|
* they will be resent after the driver comes back up. That *may*
|
|
* cause packet duplication, but that is preferable over packet loss.
|
|
*/
|
|
ethif->ethif_snd.es_unsentp = ðif->ethif_snd.es_head;
|
|
|
|
/*
|
|
* We fully restart the receive queue, because all receive requests
|
|
* have been forgotten by ndev as well now and it is easier to simply
|
|
* reconstruct the receive queue in its entirety later on.
|
|
*/
|
|
ethif_drain(ethif);
|
|
|
|
/* Make sure we do not attempt to initiate new requests for now. */
|
|
ethif->ethif_flags &= ~ETHIFF_FIRST_CONF;
|
|
ethif->ethif_flags |= ETHIFF_DISABLED;
|
|
}
|
|
|
|
/*
|
|
* Dequeue and discard the packet at the head of the send queue.
|
|
*/
|
|
static void
|
|
ethif_dequeue_send(struct ethif * ethif)
|
|
{
|
|
struct pbuf *pref, *pbuf, **pnext;
|
|
unsigned int count, spares;
|
|
|
|
/*
|
|
* The send queue is a linked list of reference buffers, each of which
|
|
* links to the actual packet. Dequeue the first reference buffer.
|
|
*/
|
|
pref = ethif->ethif_snd.es_head;
|
|
assert(pref != NULL);
|
|
|
|
pnext = pchain_end(pref);
|
|
|
|
if (ethif->ethif_snd.es_unsentp == pnext)
|
|
ethif->ethif_snd.es_unsentp = ðif->ethif_snd.es_head;
|
|
|
|
if ((ethif->ethif_snd.es_head = *pnext) == NULL)
|
|
ethif->ethif_snd.es_tailp = ðif->ethif_snd.es_head;
|
|
|
|
/* Do this before possibly calling pbuf_clen() below.. */
|
|
*pnext = NULL;
|
|
|
|
/*
|
|
* If we never made a copy of the original packet, we now have it
|
|
* pointed to by a reference buffer. If so, decrease the reference
|
|
* count of the actual packet, thereby freeing it if lwIP itself was
|
|
* already done with. Otherwise, the copy of the packet is the
|
|
* reference buffer itself. In both cases we need to free that buffer.
|
|
*/
|
|
if (pref->type == PBUF_REF) {
|
|
pbuf = (struct pbuf *)pref->payload;
|
|
|
|
pbuf_free(pbuf);
|
|
|
|
count = pref->len;
|
|
} else
|
|
count = pbuf_clen(pref);
|
|
|
|
assert(count > 0);
|
|
assert(ethif->ethif_snd.es_count >= count);
|
|
ethif->ethif_snd.es_count -= count;
|
|
|
|
if (ethif->ethif_snd.es_count >= ETHIF_PBUF_MIN)
|
|
spares = count;
|
|
else if (ethif->ethif_snd.es_count + count > ETHIF_PBUF_MIN)
|
|
spares = ethif->ethif_snd.es_count + count - ETHIF_PBUF_MIN;
|
|
else
|
|
spares = 0;
|
|
|
|
assert(ethif_spares >= spares);
|
|
ethif_spares -= spares;
|
|
|
|
/* Free the reference buffer as well. */
|
|
pbuf_free(pref);
|
|
|
|
/* If the send queue is now empty, clear the IFF_OACTIVE flag. */
|
|
if (ethif->ethif_snd.es_head == NULL)
|
|
ifdev_update_ifflags(ðif->ethif_ifdev,
|
|
ifdev_get_ifflags(ðif->ethif_ifdev) & ~IFF_OACTIVE);
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that a network device driver has been permanently
|
|
* shut down. Remove the corresponding ethernet interface from the system.
|
|
*/
|
|
void
|
|
ethif_remove(struct ethif * ethif)
|
|
{
|
|
int r;
|
|
|
|
/* Clear the send and receive queues. */
|
|
while (ethif->ethif_snd.es_head != NULL)
|
|
ethif_dequeue_send(ethif);
|
|
|
|
ethif_drain(ethif);
|
|
|
|
/* Let the ifdev module deal with most other removal aspects. */
|
|
if ((r = ifdev_remove(ðif->ethif_ifdev)) != OK)
|
|
panic("unable to remove ethernet interface: %d", r);
|
|
|
|
/* Finally, readd the ethif object to the free list. */
|
|
SIMPLEQ_INSERT_HEAD(ðif_freelist, ethif, ethif_next);
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that the (oldest) pending configuration request has
|
|
* completed with the given result.
|
|
*/
|
|
void
|
|
ethif_configured(struct ethif * ethif, int32_t result)
|
|
{
|
|
|
|
/*
|
|
* The driver is not supposed to return failure in response to a
|
|
* configure result. If it does, we have no proper way to recover, as
|
|
* we may already have applied part of the new configuration to netif.
|
|
* For now, just report failure and then pretend success.
|
|
*/
|
|
if (result < 0) {
|
|
printf("LWIP: driver '%s' replied with conf result %d\n",
|
|
ethif_get_name(ethif), result);
|
|
|
|
result = 0;
|
|
}
|
|
|
|
if (ethif->ethif_flags & ETHIFF_FIRST_CONF)
|
|
ethif->ethif_flags &= ~ETHIFF_FIRST_CONF;
|
|
else
|
|
ethif_post_conf(ethif);
|
|
|
|
/*
|
|
* For now, the result is simply a boolean value indicating whether the
|
|
* driver is using the all-multicast receive mode instead of the
|
|
* multicast-list receive mode. We can turn it into a bitmap later.
|
|
*/
|
|
if (result != 0) {
|
|
ethif->ethif_active.nconf_mode &= ~NDEV_MODE_MCAST_LIST;
|
|
ethif->ethif_active.nconf_mode |= NDEV_MODE_MCAST_ALL;
|
|
}
|
|
|
|
/* The interface flags may have changed now, so update them. */
|
|
ethif_update_ifflags(ethif);
|
|
|
|
/* Regular operation will resume from the polling function. */
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that the first packet on the send queue has been
|
|
* successfully transmitted with 'result' set to OK, or dropped if 'result' is
|
|
* negative. The latter may happen if the interface was taken down while there
|
|
* were still packets in transit.
|
|
*/
|
|
void
|
|
ethif_sent(struct ethif * ethif, int32_t result)
|
|
{
|
|
|
|
ethif_dequeue_send(ethif);
|
|
|
|
if (result < 0)
|
|
ifdev_output_drop(ðif->ethif_ifdev);
|
|
|
|
/* More requests may be sent from the polling function now. */
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports that the first buffer on the receive queue has been
|
|
* filled with a packet of 'result' bytes, or if 'result' is negative, the
|
|
* receive request has been aborted.
|
|
*/
|
|
void
|
|
ethif_received(struct ethif * ethif, int32_t result)
|
|
{
|
|
struct pbuf *pbuf, *pwalk, **pnext;
|
|
size_t left;
|
|
|
|
/*
|
|
* Start by removing the first buffer chain off the receive queue. The
|
|
* ndev layer guarantees that there ever was a receive request at all.
|
|
*/
|
|
if ((pbuf = ethif->ethif_rcv.er_head) == NULL)
|
|
panic("driver received packet but queue empty");
|
|
|
|
pnext = pchain_end(pbuf);
|
|
|
|
if ((ethif->ethif_rcv.er_head = *pnext) == NULL)
|
|
ethif->ethif_rcv.er_tailp = ðif->ethif_rcv.er_head;
|
|
*pnext = NULL;
|
|
|
|
/* Decide if we can and should deliver a packet to the layers above. */
|
|
if (result <= 0 || !ethif_can_recv(ethif)) {
|
|
pbuf_free(pbuf);
|
|
|
|
return;
|
|
}
|
|
|
|
if (result > pbuf->tot_len) {
|
|
printf("LWIP: driver '%s' returned bad packet size (%zd)\n",
|
|
ethif_get_name(ethif), (ssize_t)result);
|
|
|
|
pbuf_free(pbuf);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The packet often does not use all of the buffers, or at least not
|
|
* all of the last buffer. Adjust lengths for the buffers that contain
|
|
* part of the packet, and free the remaining (unused) buffers, if any.
|
|
*/
|
|
left = (size_t)result;
|
|
|
|
for (pwalk = pbuf; ; pwalk = pwalk->next) {
|
|
pwalk->tot_len = left;
|
|
if (pwalk->len > left)
|
|
pwalk->len = left;
|
|
left -= pwalk->len;
|
|
if (left == 0)
|
|
break;
|
|
}
|
|
|
|
if (pwalk->next != NULL) {
|
|
pbuf_free(pwalk->next);
|
|
|
|
pwalk->next = NULL;
|
|
}
|
|
|
|
/*
|
|
* Finally, hand off the packet to the layers above. We go through
|
|
* ifdev so that it can pass the packet to BPF devices and update
|
|
* statistics and all that.
|
|
*/
|
|
ifdev_input(ðif->ethif_ifdev, pbuf, NULL /*netif*/,
|
|
TRUE /*to_bpf*/);
|
|
}
|
|
|
|
/*
|
|
* The ndev layer reports a network driver status update. If anything has
|
|
* changed since the last status, we may have to take action. The given
|
|
* statistics counters are relative to the previous status report.
|
|
*/
|
|
void
|
|
ethif_status(struct ethif * ethif, uint32_t link, uint32_t media,
|
|
uint32_t oerror, uint32_t coll, uint32_t ierror, uint32_t iqdrop)
|
|
{
|
|
struct if_data *ifdata;
|
|
|
|
ethif_set_status(ethif, link, media);
|
|
|
|
ifdata = ifdev_get_ifdata(ðif->ethif_ifdev);
|
|
ifdata->ifi_oerrors += oerror;
|
|
ifdata->ifi_collisions += coll;
|
|
ifdata->ifi_ierrors += ierror;
|
|
ifdata->ifi_iqdrops += iqdrop;
|
|
}
|
|
|
|
/*
|
|
* Set NetBSD-style interface flags (IFF_) for an ethernet interface.
|
|
*/
|
|
static int
|
|
ethif_set_ifflags(struct ifdev * ifdev, unsigned int ifflags)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
uint32_t mode, flags;
|
|
|
|
/*
|
|
* We do not support IFF_NOARP at this time, because lwIP does not: the
|
|
* idea of IFF_NOARP is that only static ARP entries are used, but lwIP
|
|
* does not support separating static from dynamic ARP operation. The
|
|
* flag does not appear to be particularly widely used anyway.
|
|
*/
|
|
if ((ifflags & ~(IFF_UP | IFF_DEBUG | IFF_LINK0 | IFF_LINK1 |
|
|
IFF_LINK2)) != 0)
|
|
return EINVAL;
|
|
|
|
mode = ethif->ethif_wanted.nconf_mode;
|
|
if ((ifflags & IFF_UP) && mode == NDEV_MODE_DOWN) {
|
|
mode = NDEV_MODE_UP;
|
|
|
|
/* Always enable broadcast receipt when supported. */
|
|
if (ethif->ethif_caps & NDEV_CAP_BCAST)
|
|
mode |= NDEV_MODE_BCAST;
|
|
|
|
if (ifdev_is_promisc(ifdev))
|
|
mode |= NDEV_MODE_PROMISC;
|
|
|
|
/*
|
|
* The multicast flags will be set right before we send the
|
|
* request to the driver.
|
|
*/
|
|
} else if (!(ifflags & IFF_UP) && mode != NDEV_MODE_DOWN)
|
|
ethif->ethif_wanted.nconf_mode = NDEV_MODE_DOWN;
|
|
|
|
if (mode != ethif->ethif_wanted.nconf_mode) {
|
|
ethif->ethif_wanted.nconf_mode = mode;
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_MODE;
|
|
}
|
|
|
|
/*
|
|
* Some of the interface flags (UP, DEBUG, PROMISC, LINK[0-2]) are a
|
|
* reflection of the intended state as set by userland before, so that
|
|
* a userland utility will never not see the flag it just set (or the
|
|
* other way around). These flags therefore do not necessarily reflect
|
|
* what is actually going on at that moment. We cannot have both.
|
|
*/
|
|
flags = 0;
|
|
if (ifflags & IFF_DEBUG)
|
|
flags |= NDEV_FLAG_DEBUG;
|
|
if (ifflags & IFF_LINK0)
|
|
flags |= NDEV_FLAG_LINK0;
|
|
if (ifflags & IFF_LINK1)
|
|
flags |= NDEV_FLAG_LINK1;
|
|
if (ifflags & IFF_LINK2)
|
|
flags |= NDEV_FLAG_LINK2;
|
|
|
|
if (flags != ethif->ethif_wanted.nconf_flags) {
|
|
ethif->ethif_wanted.nconf_flags = flags;
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_FLAGS;
|
|
}
|
|
|
|
/* The changes will be picked up from the polling function. */
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Convert a bitmask of ndev-layer capabilities (NDEV_CAP_) to NetBSD-style
|
|
* interface capabilities (IFCAP_).
|
|
*/
|
|
static uint64_t
|
|
ethif_cap_to_ifcap(uint32_t caps)
|
|
{
|
|
uint64_t ifcap;
|
|
|
|
ifcap = 0;
|
|
if (caps & NDEV_CAP_CS_IP4_TX)
|
|
ifcap |= IFCAP_CSUM_IPv4_Tx;
|
|
if (caps & NDEV_CAP_CS_IP4_RX)
|
|
ifcap |= IFCAP_CSUM_IPv4_Rx;
|
|
if (caps & NDEV_CAP_CS_UDP_TX)
|
|
ifcap |= IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv6_Tx;
|
|
if (caps & NDEV_CAP_CS_UDP_RX)
|
|
ifcap |= IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv6_Rx;
|
|
if (caps & NDEV_CAP_CS_TCP_TX)
|
|
ifcap |= IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv6_Tx;
|
|
if (caps & NDEV_CAP_CS_TCP_RX)
|
|
ifcap |= IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv6_Rx;
|
|
|
|
return ifcap;
|
|
}
|
|
|
|
/*
|
|
* Retrieve potential and enabled NetBSD-style interface capabilities (IFCAP_).
|
|
*/
|
|
static void
|
|
ethif_get_ifcap(struct ifdev * ifdev, uint64_t * ifcap, uint64_t * ifena)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
*ifcap = ethif_cap_to_ifcap(ethif->ethif_caps);
|
|
*ifena = ethif_cap_to_ifcap(ethif->ethif_wanted.nconf_caps);
|
|
}
|
|
|
|
/*
|
|
* Set NetBSD-style enabled interface capabilities (IFCAP_).
|
|
*/
|
|
static int
|
|
ethif_set_ifcap(struct ifdev * ifdev, uint64_t ifcap)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
unsigned int flags;
|
|
uint32_t caps;
|
|
|
|
if (ifcap & ~(IFCAP_CSUM_IPv4_Tx | IFCAP_CSUM_IPv4_Rx |
|
|
IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv6_Tx |
|
|
IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv6_Rx |
|
|
IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv6_Tx |
|
|
IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv6_Rx))
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Some IPv4/IPv6 flags need to be set together in order to be picked
|
|
* up. Unfortunately, that is all we can do given that lwIP does not
|
|
* distinguish IPv4/IPv6 when it comes to TCP/UDP checksum flags.
|
|
*/
|
|
caps = 0;
|
|
if (ifcap & IFCAP_CSUM_IPv4_Tx)
|
|
caps |= NDEV_CAP_CS_IP4_TX;
|
|
if (ifcap & IFCAP_CSUM_IPv4_Rx)
|
|
caps |= NDEV_CAP_CS_IP4_RX;
|
|
if ((ifcap & (IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv6_Tx)) ==
|
|
(IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv6_Tx))
|
|
caps |= NDEV_CAP_CS_UDP_TX;
|
|
if ((ifcap & (IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv6_Rx)) ==
|
|
(IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv6_Rx))
|
|
caps |= NDEV_CAP_CS_UDP_RX;
|
|
if ((ifcap & (IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv6_Tx)) ==
|
|
(IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv6_Tx))
|
|
caps |= NDEV_CAP_CS_TCP_TX;
|
|
if ((ifcap & (IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv6_Rx)) ==
|
|
(IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv6_Rx))
|
|
caps |= NDEV_CAP_CS_TCP_RX;
|
|
|
|
/*
|
|
* When changing checksumming capabilities, we have to make sure that
|
|
* we only ever checksum too much and never too little. This means
|
|
* that we enable any checksum options in netif here, and disable any
|
|
* checksum options in netif only after driver configuration.
|
|
*
|
|
* Note that we have to draw the line somewhere with this kind of
|
|
* self-protection, and that line is short of TCP retransmission: we
|
|
* see it as lwIP's job to compute checksums for retransmitted TCP
|
|
* packets if they were saved across checksum changes. Even though
|
|
* lwIP may not care, there is little we can do about that anyway.
|
|
*/
|
|
if (ethif->ethif_wanted.nconf_caps != caps) {
|
|
flags = ethif_get_netif(ethif)->chksum_flags;
|
|
|
|
if (!(caps & NDEV_CAP_CS_IP4_TX))
|
|
flags |= NETIF_CHECKSUM_GEN_IP;
|
|
if (!(caps & NDEV_CAP_CS_IP4_RX))
|
|
flags |= NETIF_CHECKSUM_CHECK_IP;
|
|
if (!(caps & NDEV_CAP_CS_UDP_TX))
|
|
flags |= NETIF_CHECKSUM_GEN_UDP;
|
|
if (!(caps & NDEV_CAP_CS_UDP_RX))
|
|
flags |= NETIF_CHECKSUM_CHECK_UDP;
|
|
if (!(caps & NDEV_CAP_CS_TCP_TX))
|
|
flags |= NETIF_CHECKSUM_GEN_TCP;
|
|
if (!(caps & NDEV_CAP_CS_TCP_RX))
|
|
flags |= NETIF_CHECKSUM_CHECK_TCP;
|
|
|
|
NETIF_SET_CHECKSUM_CTRL(ethif_get_netif(ethif), flags);
|
|
|
|
ethif->ethif_wanted.nconf_caps = caps;
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_CAPS;
|
|
}
|
|
|
|
/* The changes will be picked up from the polling function. */
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Retrieve NetBSD-style interface media type (IFM_). Return both the current
|
|
* media type selection and the driver-reported active media type.
|
|
*/
|
|
static void
|
|
ethif_get_ifmedia(struct ifdev * ifdev, int * ifcurrent, int * ifactive)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
/*
|
|
* For the current select, report back whatever the user gave us, even
|
|
* if it has not reached the driver at all yet.
|
|
*/
|
|
*ifcurrent = (int)ethif->ethif_wanted.nconf_media;
|
|
*ifactive = (int)ethif->ethif_media;
|
|
}
|
|
|
|
/*
|
|
* Set current NetBSD-style interface media type (IFM_).
|
|
*/
|
|
static int
|
|
ethif_set_ifmedia(struct ifdev * ifdev, int ifmedia)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
/*
|
|
* We currently completely lack the infrastructure to suspend the
|
|
* current IOCTL call until the driver replies (or disappears).
|
|
* Therefore we have no choice but to return success here, even if the
|
|
* driver cannot accept the change. The driver does notify us of media
|
|
* changes, so the user may observe the new active media type later.
|
|
* Also note that the new media type may not be the requested type,
|
|
* which is why we do not perform any checks against the wanted or
|
|
* active media types.
|
|
*/
|
|
ethif->ethif_wanted.nconf_media = (uint32_t)ifmedia;
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_MEDIA;
|
|
|
|
/* The change will be picked up from the polling function. */
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Enable or disable promiscuous mode on the interface.
|
|
*/
|
|
static void
|
|
ethif_set_promisc(struct ifdev * ifdev, int promisc)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
if (ethif->ethif_wanted.nconf_mode != NDEV_MODE_DOWN) {
|
|
if (promisc)
|
|
ethif->ethif_wanted.nconf_mode |= NDEV_MODE_PROMISC;
|
|
else
|
|
ethif->ethif_wanted.nconf_mode &= ~NDEV_MODE_PROMISC;
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_MODE;
|
|
}
|
|
|
|
/* The change will be picked up from the polling function. */
|
|
}
|
|
|
|
/*
|
|
* Set the hardware address on the interface.
|
|
*/
|
|
static int
|
|
ethif_set_hwaddr(struct ifdev * ifdev, const uint8_t * hwaddr)
|
|
{
|
|
struct ethif *ethif = (struct ethif *)ifdev;
|
|
|
|
if (!(ethif->ethif_caps & NDEV_CAP_HWADDR))
|
|
return EINVAL;
|
|
|
|
memcpy(ðif->ethif_wanted.nconf_hwaddr.nhwa_addr, hwaddr,
|
|
ETHARP_HWADDR_LEN);
|
|
ethif->ethif_wanted.nconf_set |= NDEV_SET_HWADDR;
|
|
|
|
/* The change will be picked up from the polling function. */
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Set the Maximum Transmission Unit for this interface. Return TRUE if the
|
|
* new value is acceptable, in which case the caller will do the rest. Return
|
|
* FALSE otherwise.
|
|
*/
|
|
static int
|
|
ethif_set_mtu(struct ifdev * ifdev __unused, unsigned int mtu)
|
|
{
|
|
|
|
return (mtu <= ETHIF_MAX_MTU);
|
|
}
|
|
|
|
static const struct ifdev_ops ethif_ops = {
|
|
.iop_init = ethif_init_netif,
|
|
.iop_input = netif_input,
|
|
.iop_output = ethif_output,
|
|
.iop_output_v4 = etharp_output,
|
|
.iop_output_v6 = ethip6_output,
|
|
.iop_hdrcmplt = ethif_hdrcmplt,
|
|
.iop_poll = ethif_poll,
|
|
.iop_set_ifflags = ethif_set_ifflags,
|
|
.iop_get_ifcap = ethif_get_ifcap,
|
|
.iop_set_ifcap = ethif_set_ifcap,
|
|
.iop_get_ifmedia = ethif_get_ifmedia,
|
|
.iop_set_ifmedia = ethif_set_ifmedia,
|
|
.iop_set_promisc = ethif_set_promisc,
|
|
.iop_set_hwaddr = ethif_set_hwaddr,
|
|
.iop_set_mtu = ethif_set_mtu,
|
|
};
|