genode/repos/dde_linux/src/lib/usb/nic/nic.cc

740 lines
14 KiB
C++

/*
* \brief Glue code for Linux network drivers
* \author Sebastian Sumpf
* \date 2012-07-05
*/
/*
* Copyright (C) 2012-2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#include <base/rpc_server.h>
#include <base/snprintf.h>
#include <nic_session/nic_session.h>
#include <cap_session/connection.h>
#include <nic/xml_node.h>
#include <util/xml_node.h>
#include <os/config.h>
#include <extern_c_begin.h>
#include <lx_emul.h>
#include <linux/usb.h>
#include <linux/usb/usbnet.h>
#include <extern_c_end.h>
#include <nic/component.h>
#include "signal.h"
static Signal_helper *_signal = 0;
enum {
HEAD_ROOM = 8, /* head room in skb in bytes */
MAC_LEN = 17, /* 12 number and 6 colons */
};
/**
* Internal alloc function
*/
struct sk_buff *_alloc_skb(unsigned int size, bool tx = true);
/**
* Skb-bitmap allocator
*/
class Skb
{
private:
unsigned const _entries;
sk_buff *_buf;
unsigned *_free;
unsigned _idx;
enum { ENTRY_ELEMENT_SIZE = sizeof(unsigned) * 8 };
public:
Skb(unsigned const entries, unsigned const buffer_size)
:
_entries(entries), _idx(0)
{
unsigned const size = _entries / sizeof(unsigned);
_buf = (sk_buff *)kmalloc(sizeof(sk_buff) * _entries, GFP_KERNEL);
_free = (unsigned *)kmalloc(sizeof(unsigned) * size, GFP_KERNEL);
Genode::memset(_free, 0xff, size * sizeof(unsigned));
for (unsigned i = 0; i < _entries; i++)
_buf[i].start = (unsigned char *)kmalloc(buffer_size + NET_IP_ALIGN, GFP_NOIO);
}
sk_buff *alloc()
{
unsigned const IDX = _entries / ENTRY_ELEMENT_SIZE;
for (register unsigned i = 0; i < IDX; i++) {
if (_free[_idx] != 0) {
unsigned msb = Genode::log2(_free[_idx]);
_free[_idx] ^= (1 << msb);
sk_buff *r = &_buf[(_idx * ENTRY_ELEMENT_SIZE) + msb];
r->data = r->start;
r->phys = 0;
r->cloned = 0;
r->clone = 0;
r->len = 0;
return r;
}
_idx = (_idx + 1) % IDX;
}
return 0;
}
void free(sk_buff *buf)
{
unsigned entry = buf - &_buf[0];
if (&_buf[0] > buf || entry > _entries)
return;
_idx = entry / ENTRY_ELEMENT_SIZE;
_free[_idx] |= (1 << (entry % ENTRY_ELEMENT_SIZE));
}
};
/* send/receive skb allocators */
static Skb *skb_tx(unsigned const elements = 0, unsigned const buffer_size = 0)
{
static Skb _skb(elements, buffer_size);
return &_skb;
}
static Skb *skb_rx(unsigned const elements = 0, unsigned const buffer_size = 0)
{
static Skb _skb(elements, buffer_size);
return &_skb;
}
/**
* Prototype of fixup function
*/
extern "C" {
typedef struct sk_buff* (*fixup_t)(struct usbnet *, struct sk_buff *, gfp_t);
}
/**
* Net_device to session glue code
*/
class Nic_device : public Nic::Device
{
public:
struct net_device *_ndev; /* Linux-net device */
fixup_t _tx_fixup;
bool const _burst;
bool _has_link { false };
public:
Nic_device(struct net_device *ndev)
:
_ndev(ndev),
/* XXX should be configurable instead of guessing burst mode */
_burst(((usbnet *)netdev_priv(ndev))->rx_urb_size > 2048)
{
struct usbnet *dev = (usbnet *)netdev_priv(_ndev);
/* initialize skb allocators */
unsigned urb_cnt = dev->rx_urb_size <= 2048 ? 128 : 64;
skb_rx(urb_cnt, dev->rx_urb_size);
skb_tx(urb_cnt, dev->rx_urb_size);
if (!burst()) return;
/*
* Retrieve 'tx_fixup' function from driver and set it to zero,
* so it cannot be called by the actual driver. Required for
* burst mode.
*/
_tx_fixup = dev->driver_info->tx_fixup;
dev->driver_info->tx_fixup = 0;
}
/**
* Add device
*/
static Nic_device *add(struct net_device *ndev) {
return new (Genode::env()->heap()) Nic_device(ndev); }
/**
* Report link state
*/
void link_state(bool link)
{
/* only report changes of the link state */
if (link == _has_link)
return;
_has_link = link;
if (_session)
_session->link_state_changed();
}
/**********************
** Device interface **
**********************/
bool link_state() override { return _has_link; }
/**
* Submit packet to driver
*/
bool tx(Genode::addr_t virt, Genode::size_t size)
{
sk_buff *skb;
if (!(skb = _alloc_skb(size + HEAD_ROOM)))
return false;
skb->len = size;
skb->data += HEAD_ROOM;
Genode::memcpy(skb->data, (void *)virt, skb->len);
tx_skb(skb);
return true;
}
/**
* Alloc an SKB
*/
sk_buff *alloc_skb()
{
struct usbnet *dev = (usbnet *)netdev_priv(_ndev);
sk_buff *skb;
if (!(skb = _alloc_skb(dev->rx_urb_size)))
return 0;
skb->len = 0;
return skb;
}
/**
* Submit SKB to the driver
*/
void tx_skb(sk_buff *skb)
{
struct usbnet *dev = (usbnet *)netdev_priv(_ndev);
unsigned long dropped = dev->net->stats.tx_dropped;
_ndev->netdev_ops->ndo_start_xmit(skb, _ndev);
if (dropped < dev->net->stats.tx_dropped)
PWRN("Dropped SKB");
}
/**
* Call tx_fixup function of driver
*/
void tx_fixup(struct sk_buff *skb)
{
struct usbnet *dev = (usbnet *)netdev_priv(_ndev);
if(!_tx_fixup || !_tx_fixup(dev, skb, 0))
PERR("Tx fixup error");
}
/**
* Fill an SKB with 'data' if 'size', return false if SKB is greater than
* 'end'
*/
bool skb_fill(struct sk_buff *skb, unsigned char *data, Genode::size_t size, unsigned char *end)
{
Genode::addr_t align = ((Genode::addr_t)(data + 3) & ~3);
skb->truesize = skb->data == 0 ? 0 : (unsigned char*)align - data;
data = skb->data == 0 ? data : (unsigned char*)align;
skb->start = data;
data += HEAD_ROOM;
skb->len = size;
skb->data = data;
skb->end = skb->tail = data + size;
skb->truesize += (skb->end - skb->start);
return skb->end >= end ? false : true;
}
/**
* Submit packet for session
*/
inline void rx(sk_buff *skb) { _session->rx((Genode::addr_t)skb->data, skb->len); }
/**
* Return mac address
*/
Nic::Mac_address mac_address()
{
Nic::Mac_address m;
Genode::memcpy(&m, _ndev->_dev_addr, ETH_ALEN);
return m;
}
bool burst() { return _burst; }
};
/* XXX support multiple devices */
static Nic_device *_nic = 0;
void Nic::init(Server::Entrypoint &ep) {
_signal = new (Genode::env()->heap()) Signal_helper(ep); }
/***********************
** linux/netdevice.h **
***********************/
int register_netdev(struct net_device *ndev)
{
using namespace Genode;
static bool announce = false;
int err = -ENODEV;
Nic_device *nic = Nic_device::add(ndev);
/* XXX: move to 'main' */
if (!announce) {
static Nic::Root root(_signal->ep(), env()->heap(), nic);
announce = true;
ndev->state |= 1 << __LINK_STATE_START;
netif_carrier_off(ndev);
if ((err = ndev->netdev_ops->ndo_open(ndev)))
return err;
if (ndev->netdev_ops->ndo_set_rx_mode)
ndev->netdev_ops->ndo_set_rx_mode(ndev);
/*
if(ndev->netdev_ops->ndo_change_mtu)
ndev->netdev_ops->ndo_change_mtu(ndev, 4000);
*/
_nic = nic;
env()->parent()->announce(_signal->ep().rpc_ep().manage(&root));
}
return err;
}
int netif_running(const struct net_device *dev)
{
return dev->state & (1 << __LINK_STATE_START);
}
int netif_device_present(struct net_device *dev) { return 1; }
int netif_carrier_ok(const struct net_device *dev)
{
return !(dev->state & (1 << __LINK_STATE_NOCARRIER));
}
void netif_carrier_on(struct net_device *dev)
{
dev->state &= ~(1 << __LINK_STATE_NOCARRIER);
if (_nic)
_nic->link_state(true);
}
void netif_carrier_off(struct net_device *dev)
{
dev->state |= 1 << __LINK_STATE_NOCARRIER;
if (_nic)
_nic->link_state(false);
}
#ifdef GENODE_NET_STAT
#include <nic/stat.h>
static Timer::Connection _timer;
static Nic::Measurement _stat(_timer);
#endif
int netif_rx(struct sk_buff *skb)
{
if (_nic && _nic->session()) {
_nic->rx(skb);
}
#ifdef GENODE_NET_STAT
else if (_nic) {
try {
_stat.data(new (skb->data) Net::Ethernet_frame(skb->len), skb->len);
} catch(Net::Ethernet_frame::No_ethernet_frame) {
PWRN("No ether frame");
}
}
#endif
dev_kfree_skb(skb);
return NET_RX_SUCCESS;
}
/********************
** linux/skbuff.h **
********************/
struct sk_buff *_alloc_skb(unsigned int size, bool tx)
{
sk_buff *skb = tx ? skb_tx()->alloc() : skb_rx()->alloc();
if (!skb)
return 0;
size = (size + 3) & ~(0x3);
skb->end = skb->start + size;
skb->tail = skb->start;
skb->truesize = size;
return skb;
}
struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
{
/*
* Note: This is only called for RX skb's by the driver
*/
struct sk_buff *skb = _alloc_skb(size, false);
return skb;
}
struct sk_buff *netdev_alloc_skb_ip_align(struct net_device *dev, unsigned int length)
{
struct sk_buff *s = _alloc_skb(length + NET_IP_ALIGN, false);
if (s && dev->net_ip_align) {
s->data += NET_IP_ALIGN;
s->tail += NET_IP_ALIGN;
}
return s;
}
void dev_kfree_skb(struct sk_buff *skb)
{
dde_kit_log(DEBUG_SKB, "free skb: %p start: %p cloned: %d",
skb, skb->start, skb->cloned);
if (skb->cloned) {
skb->start = skb->clone;
skb->cloned = false;
skb_rx()->free(skb);
return;
}
skb_tx()->free(skb);
skb_rx()->free(skb);
}
void dev_kfree_skb_any(struct sk_buff *skb) { dev_kfree_skb(skb); }
void kfree_skb(struct sk_buff *skb) { dev_kfree_skb(skb); }
/**
* Reserve 'len'
*/
void skb_reserve(struct sk_buff *skb, int len)
{
if ((skb->data + len) > skb->end) {
PERR("Error resevring SKB data: skb: %p data: %p end: %p len: %d",
skb, skb->data, skb->end, skb->len);
return;
}
skb->data += len;
dde_kit_log(DEBUG_SKB, "skb: %p slen: %u len: %d", skb, skb->len, len);
}
/**
* Prepend 'len'
*/
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
if((skb->data - len) < skb->start) {
PERR("Error SKB head room too small: %p data: %p start: %p len: %u",
skb, skb->data, skb->start, len);
return 0;
}
skb->len += len;
skb->data -= len;
dde_kit_log(DEBUG_SKB, "skb: %p slen: %u len: %u", skb, skb->len, len);
return skb->data;
}
/**
* Append 'len'
*/
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
if ((skb->data + len > skb->end)) {
PERR("Error increasing SKB length: skb: %p data: %p end: %p len: %u",
skb, skb->data, skb->end, len);
return 0;
}
unsigned char *old = skb_tail_pointer(skb);
skb->len += len;
skb->tail += len;
dde_kit_log(DEBUG_SKB, "skb: %p slen: %u len: %u", skb, skb->len, len);
return old;
}
/**
* Return current head room
*/
unsigned int skb_headroom(const struct sk_buff *skb)
{
return skb->data - skb->start;
}
int skb_tailroom(const struct sk_buff *skb)
{
return skb->end - skb->tail;
}
/**
* Take 'len' from front
*/
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
if (len > skb->len) {
PERR("Error try to pull too much: skb: %p len: %u pull len: %u",
skb, skb->len, len);
return 0;
}
skb->len -= len;
dde_kit_log(DEBUG_SKB, "skb: %p slen: %u len: %u", skb, skb->len, len);
return skb->data += len;
}
/**
* Set 'len' and 'tail'
*/
void skb_trim(struct sk_buff *skb, unsigned int len)
{
if (skb->len <= len) {
PERR("Error trimming to %u bytes skb: %p data: %p start: %p len %u ret: %p",
len, skb, skb->data, skb->start, skb->len, __builtin_return_address((0)));
return;
}
skb->len = len;
skb_set_tail_pointer(skb, len);
dde_kit_log(DEBUG_SKB, "skb: %p slen: %u len: %u", skb, skb->len, len);
}
/**
* Clone skb
*/
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
{
sk_buff *c;
if (!(c = alloc_skb(0,0)))
return 0;
unsigned char *start = c->start;
*c = *skb;
/* save old start pointer */
c->cloned = 1;
c->clone = start;
return c;
}
int skb_header_cloned(const struct sk_buff *skb)
{
return skb->cloned;
}
void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
skb->tail = skb->data + offset;
}
unsigned char *skb_tail_pointer(const struct sk_buff *skb)
{
return skb->tail;
}
/**
* Dummy for shared info
*/
struct skb_shared_info *skb_shinfo(struct sk_buff const * /* skb */)
{
static skb_shared_info _s = { 0 };
return &_s;
}
/**
* Init list head
*/
void skb_queue_head_init(struct sk_buff_head *list)
{
static int count_x = 0;
list->prev = list->next = (sk_buff *)list;
list->qlen = 0;
}
/**
* Add to tail of queue
*/
void __skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
{
newsk->next = (sk_buff *)list;
newsk->prev = list->prev;
list->prev->next = newsk;
list->prev = newsk;
list->qlen++;
}
void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk) {
__skb_queue_tail(list, newsk); }
/**
* Remove skb from queue
*/
void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list)
{
if (!list->qlen)
return;
skb->prev->next = skb->next;
skb->next->prev = skb->prev;
skb->next = skb->prev = 0;
list->qlen--;
}
/**
* Remove from head of queue
*/
struct sk_buff *skb_dequeue(struct sk_buff_head *list)
{
if (list->qlen == 0)
return 0;
sk_buff *skb = list->next;
__skb_unlink(skb, list);
return skb;
}
/**********************
** linux/inerrupt.h **
**********************/
static void snprint_mac(u8 *buf, u8 *mac)
{
for (int i = 0; i < ETH_ALEN; i++)
{
Genode::snprintf((char *)&buf[i * 3], 3, "%02x", mac[i]);
if ((i * 3) < MAC_LEN)
buf[(i * 3) + 2] = ':';
}
buf[MAC_LEN] = 0;
}
/*************************
** linux/etherdevice.h **
*************************/
void eth_hw_addr_random(struct net_device *dev)
{
random_ether_addr(dev->_dev_addr);
}
void eth_random_addr(u8 *addr)
{
random_ether_addr(addr);
}
void random_ether_addr(u8 *addr)
{
using namespace Genode;
u8 str[MAC_LEN + 1];
u8 fallback[] = { 0x2e, 0x60, 0x90, 0x0c, 0x4e, 0x01 };
Nic::Mac_address mac;
/* try using configured mac */
try {
Xml_node nic_config = config()->xml_node().sub_node("nic");
Xml_node::Attribute mac_node = nic_config.attribute("mac");
mac_node.value(&mac);
} catch (...) {
/* use fallback mac */
snprint_mac(str, fallback);
PWRN("No mac address or wrong format attribute in <nic> - using fallback (%s)",
str);
Genode::memcpy(addr, fallback, ETH_ALEN);
return;
}
/* use configured mac*/
Genode::memcpy(addr, mac.addr, ETH_ALEN);
snprint_mac(str, (u8 *)mac.addr);
PINF("Using configured mac: %s", str);
#ifdef GENODE_NET_STAT
_stat.set_mac(mac.addr);
#endif
}