|
|
GNU Mach 1.8
/*
* Linux network driver support.
*
* Copyright (C) 1996 The University of Utah and the Computer Systems
* Laboratory at the University of Utah (CSL)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Author: Shantanu Goel, University of Utah CSL
*/
/*
* INET An implementation of the TCP/IP protocol suite for the LINUX
* operating system. INET is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* Ethernet-type device handling.
*
* Version: @(#)eth.c 1.0.7 05/25/93
*
* Authors: Ross Biro, <[email protected]>
* Fred N. van Kempen, <[email protected]>
* Mark Evans, <[email protected]>
* Florian La Roche, <[email protected]>
* Alan Cox, <[email protected]>
*
* Fixes:
* Mr Linux : Arp problems
* Alan Cox : Generic queue tidyup (very tiny here)
* Alan Cox : eth_header ntohs should be htons
* Alan Cox : eth_rebuild_header missing an htons and
* minor other things.
* Tegge : Arp bug fixes.
* Florian : Removed many unnecessary functions, code cleanup
* and changes for new arp and skbuff.
* Alan Cox : Redid header building to reflect new format.
* Alan Cox : ARP only when compiled with CONFIG_INET
* Greg Page : 802.2 and SNAP stuff.
* Alan Cox : MAC layer pointers/new format.
* Paul Gortmaker : eth_copy_and_sum shouldn't csum padding.
* Alan Cox : Protect against forwarding explosions with
* older network drivers and IFF_ALLMULTI
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <sys/types.h>
#include <machine/spl.h>
#include <machine/vm_param.h>
#include <mach/mach_types.h>
#include <mach/kern_return.h>
#include <mach/mig_errors.h>
#include <mach/port.h>
#include <mach/vm_param.h>
#include <mach/notify.h>
#include <kern/kalloc.h>
#include <kern/printf.h>
#include <ipc/ipc_port.h>
#include <ipc/ipc_space.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <vm/vm_page.h>
#include <device/device_types.h>
#include <device/device_port.h>
#include <device/if_hdr.h>
#include <device/if_ether.h>
#include <device/if_hdr.h>
#include <device/net_io.h>
#include <device/device_reply.user.h>
#include <device/device_emul.h>
#include <device/ds_routines.h>
#define MACH_INCLUDE
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/malloc.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/wireless.h>
#include <linux/dev/glue/glue.h>
/* One of these is associated with each instance of a device. */
struct net_data
{
ipc_port_t port; /* device port */
struct ifnet ifnet; /* Mach ifnet structure (needed for filters) */
struct device device; /* generic device structure */
struct linux_device *dev; /* Linux network device structure */
};
/* List of sk_buffs waiting to be freed. */
static struct sk_buff_head skb_done_list;
/* Forward declarations. */
extern struct device_emulation_ops linux_net_emulation_ops;
static int print_packet_size = 0;
/* Linux kernel network support routines. */
/* Requeue packet SKB for transmission after the interface DEV
has timed out. The priority of the packet is PRI.
In Mach, we simply drop the packet like the native drivers. */
void
dev_queue_xmit (struct sk_buff *skb, struct linux_device *dev, int pri)
{
dev_kfree_skb (skb, FREE_WRITE);
}
/* Close the device DEV. */
int
dev_close (struct linux_device *dev)
{
return 0;
}
/* Network software interrupt handler. */
void
net_bh (void)
{
int len;
struct sk_buff *skb;
struct linux_device *dev;
/* Start transmission on interfaces. */
for (dev = dev_base; dev; dev = dev->next)
{
if (dev->base_addr && dev->base_addr != 0xffe0)
while (1)
{
skb = skb_dequeue (&dev->buffs[0]);
if (skb)
{
len = skb->len;
if ((*dev->hard_start_xmit) (skb, dev))
{
skb_queue_head (&dev->buffs[0], skb);
mark_bh (NET_BH);
break;
}
else if (print_packet_size)
printf ("net_bh: length %d\n", len);
}
else
break;
}
}
}
/* Free all sk_buffs on the done list.
This routine is called by the iodone thread in ds_routines.c. */
void
free_skbuffs ()
{
struct sk_buff *skb;
while (1)
{
skb = skb_dequeue (&skb_done_list);
if (skb)
{
if (skb->copy)
{
vm_map_copy_discard (skb->copy);
skb->copy = NULL;
}
if (IP_VALID (skb->reply))
{
ds_device_write_reply (skb->reply, skb->reply_type, 0, skb->len);
skb->reply = IP_NULL;
}
dev_kfree_skb (skb, FREE_WRITE);
}
else
break;
}
}
/* Allocate an sk_buff with SIZE bytes of data space. */
struct sk_buff *
alloc_skb (unsigned int size, int priority)
{
return dev_alloc_skb (size);
}
/* Free SKB. */
void
kfree_skb (struct sk_buff *skb, int priority)
{
dev_kfree_skb (skb, priority);
}
/* Allocate an sk_buff with SIZE bytes of data space. */
struct sk_buff *
dev_alloc_skb (unsigned int size)
{
struct sk_buff *skb;
unsigned char *bptr;
int len = size;
size = (size + 15) & ~15;
size += sizeof (struct sk_buff);
bptr = linux_kmalloc (size, GFP_KERNEL);
if (bptr == NULL)
return NULL;
/* XXX: In Mach, a sk_buff is located at the head,
while it's located at the tail in Linux. */
skb = bptr;
skb->dev = NULL;
skb->reply = IP_NULL;
skb->copy = NULL;
skb->len = 0;
skb->prev = skb->next = NULL;
skb->list = NULL;
skb->data = bptr + sizeof (struct sk_buff);
skb->tail = skb->data;
skb->head = skb->data;
skb->end = skb->data + len;
return skb;
}
/* Free the sk_buff SKB. */
void
dev_kfree_skb (struct sk_buff *skb, int mode)
{
unsigned flags;
/* Queue sk_buff on done list if there is a
page list attached or we need to send a reply.
Wakeup the iodone thread to process the list. */
if (skb->copy || IP_VALID (skb->reply))
{
skb_queue_tail (&skb_done_list, skb);
save_flags (flags);
thread_wakeup ((event_t) & io_done_list);
restore_flags (flags);
return;
}
linux_kfree (skb);
}
/* Accept packet SKB received on an interface. */
void
netif_rx (struct sk_buff *skb)
{
ipc_kmsg_t kmsg;
struct ether_header *eh;
struct packet_header *ph;
struct linux_device *dev = skb->dev;
assert (skb != NULL);
if (print_packet_size)
printf ("netif_rx: length %ld\n", skb->len);
/* Allocate a kernel message buffer. */
kmsg = net_kmsg_get ();
if (!kmsg)
{
dev_kfree_skb (skb, FREE_READ);
return;
}
/* Copy packet into message buffer. */
eh = (struct ether_header *) (net_kmsg (kmsg)->header);
ph = (struct packet_header *) (net_kmsg (kmsg)->packet);
memcpy (eh, skb->data, sizeof (struct ether_header));
/* packet is prefixed with a struct packet_header,
see include/device/net_status.h. */
memcpy (ph + 1, skb->data + sizeof (struct ether_header),
skb->len - sizeof (struct ether_header));
ph->type = eh->ether_type;
ph->length = (skb->len - sizeof (struct ether_header)
+ sizeof (struct packet_header));
dev_kfree_skb (skb, FREE_READ);
net_kmsg(kmsg)->sent = FALSE; /* Mark packet as received. */
/* Pass packet up to the microkernel. */
net_packet (&dev->net_data->ifnet, kmsg,
ph->length, ethernet_priority (kmsg));
}
/* Mach device interface routines. */
/* Return a send right associated with network device ND. */
static ipc_port_t
dev_to_port (void *nd)
{
return (nd
? ipc_port_make_send (((struct net_data *) nd)->port)
: IP_NULL);
}
static io_return_t
device_open (ipc_port_t reply_port, mach_msg_type_name_t reply_port_type,
dev_mode_t mode, char *name, device_t *devp)
{
io_return_t err = D_SUCCESS;
ipc_port_t notify;
struct ifnet *ifp;
struct linux_device *dev;
struct net_data *nd;
/* Search for the device. */
for (dev = dev_base; dev; dev = dev->next)
if (dev->base_addr
&& dev->base_addr != 0xffe0
&& !strcmp (name, dev->name))
break;
if (!dev)
return D_NO_SUCH_DEVICE;
/* Allocate and initialize device data if this is the first open. */
nd = dev->net_data;
if (!nd)
{
dev->net_data = nd = ((struct net_data *)
kalloc (sizeof (struct net_data)));
if (!nd)
{
err = D_NO_MEMORY;
goto out;
}
nd->dev = dev;
nd->device.emul_data = nd;
nd->device.emul_ops = &linux_net_emulation_ops;
nd->port = ipc_port_alloc_kernel ();
if (nd->port == IP_NULL)
{
err = KERN_RESOURCE_SHORTAGE;
goto out;
}
ipc_kobject_set (nd->port, (ipc_kobject_t) & nd->device, IKOT_DEVICE);
notify = ipc_port_make_sonce (nd->port);
ip_lock (nd->port);
ipc_port_nsrequest (nd->port, 1, notify, ¬ify);
assert (notify == IP_NULL);
ifp = &nd->ifnet;
ifp->if_unit = dev->name[strlen (dev->name) - 1] - '0';
ifp->if_flags = IFF_UP | IFF_RUNNING;
ifp->if_mtu = dev->mtu;
ifp->if_header_size = dev->hard_header_len;
ifp->if_header_format = dev->type;
ifp->if_address_size = dev->addr_len;
ifp->if_address = dev->dev_addr;
if_init_queues (ifp);
if (dev->open)
{
linux_intr_pri = SPL6;
if ((*dev->open) (dev))
err = D_NO_SUCH_DEVICE;
}
out:
if (err)
{
if (nd)
{
if (nd->port != IP_NULL)
{
ipc_kobject_set (nd->port, IKO_NULL, IKOT_NONE);
ipc_port_dealloc_kernel (nd->port);
}
kfree ((vm_offset_t) nd, sizeof (struct net_data));
nd = NULL;
dev->net_data = NULL;
}
}
else
{
/* IPv6 heavily relies on multicasting (especially router and
neighbor solicits and advertisements), so enable reception of
those multicast packets by setting `LINUX_IFF_ALLMULTI'. */
dev->flags |= LINUX_IFF_UP | LINUX_IFF_RUNNING | LINUX_IFF_ALLMULTI;
skb_queue_head_init (&dev->buffs[0]);
if (dev->set_multicast_list)
dev->set_multicast_list (dev);
}
if (IP_VALID (reply_port))
ds_device_open_reply (reply_port, reply_port_type,
err, dev_to_port (nd));
return MIG_NO_REPLY;
}
*devp = &nd->device;
return D_SUCCESS;
}
static io_return_t
device_write (void *d, ipc_port_t reply_port,
mach_msg_type_name_t reply_port_type, dev_mode_t mode,
recnum_t bn, io_buf_ptr_t data, unsigned int count,
int *bytes_written)
{
unsigned char *p;
int i, s;
vm_map_copy_t copy = (vm_map_copy_t) data;
char *map_data;
vm_offset_t map_addr;
vm_size_t map_size;
struct net_data *nd = d;
struct linux_device *dev = nd->dev;
struct sk_buff *skb;
kern_return_t kr;
if (count == 0 || count > dev->mtu + dev->hard_header_len)
return D_INVALID_SIZE;
/* Allocate a sk_buff. */
skb = dev_alloc_skb (count);
if (!skb)
return D_NO_MEMORY;
/* Map user data. */
kr = kmem_io_map_copyout(device_io_map, (vm_offset_t *)&map_data,
&map_addr, &map_size, copy, count);
if (kr) {
dev_kfree_skb (skb, FREE_WRITE);
return D_NO_MEMORY;
}
/* XXX The underlying physical pages of the mapping could be highmem,
for which drivers require the use of a bounce buffer. */
memcpy (skb->data, map_data, count);
kmem_io_map_deallocate (device_io_map, map_addr, map_size);
vm_map_copy_discard (copy);
skb->len = count;
skb->head = skb->data;
skb->tail = skb->data + skb->len;
skb->end = skb->tail;
skb->dev = dev;
skb->reply = reply_port;
skb->reply_type = reply_port_type;
/* Queue packet for transmission and schedule a software interrupt. */
s = splimp ();
if (dev->buffs[0].next != (struct sk_buff *) &dev->buffs[0]
|| (*dev->hard_start_xmit) (skb, dev))
{
__skb_queue_tail (&dev->buffs[0], skb);
mark_bh (NET_BH);
}
splx (s);
/* Send packet to filters. */
{
struct packet_header *packet;
struct ether_header *header;
ipc_kmsg_t kmsg;
kmsg = net_kmsg_get ();
if (kmsg != IKM_NULL)
{
/* Suitable for Ethernet only. */
header = (struct ether_header *) (net_kmsg (kmsg)->header);
packet = (struct packet_header *) (net_kmsg (kmsg)->packet);
memcpy (header, skb->data, sizeof (struct ether_header));
/* packet is prefixed with a struct packet_header,
see include/device/net_status.h. */
memcpy (packet + 1, skb->data + sizeof (struct ether_header),
skb->len - sizeof (struct ether_header));
packet->length = skb->len - sizeof (struct ether_header)
+ sizeof (struct packet_header);
packet->type = header->ether_type;
net_kmsg (kmsg)->sent = TRUE; /* Mark packet as sent. */
s = splimp ();
net_packet (&dev->net_data->ifnet, kmsg, packet->length,
ethernet_priority (kmsg));
splx (s);
}
}
return MIG_NO_REPLY;
}
static io_return_t
device_get_status (void *d, dev_flavor_t flavor, dev_status_t status,
mach_msg_type_number_t *count)
{
if (flavor == NET_FLAGS)
{
struct net_data *net = (struct net_data *) d;
if (*count != 1)
return D_INVALID_SIZE;
status[0] = net->dev->flags;
return D_SUCCESS;
}
if(flavor >= SIOCIWFIRST && flavor <= SIOCIWLAST)
{
/* handle wireless ioctl */
if(! IW_IS_GET(flavor))
return D_INVALID_OPERATION;
if(*count * sizeof(int) < sizeof(struct ifreq))
return D_INVALID_OPERATION;
struct net_data *nd = d;
struct linux_device *dev = nd->dev;
if(! dev->do_ioctl)
return D_INVALID_OPERATION;
int result;
if (flavor == SIOCGIWRANGE || flavor == SIOCGIWENCODE
|| flavor == SIOCGIWESSID || flavor == SIOCGIWNICKN
|| flavor == SIOCGIWSPY)
{
/*
* These ioctls require an `iw_point' as their argument (i.e.
* they want to return some data to userspace.
* Therefore supply some sane values and carry the data back
* to userspace right behind the `struct iwreq'.
*/
struct iw_point *iwp = &((struct iwreq *) status)->u.data;
iwp->length = *count * sizeof (dev_status_t) - sizeof (struct ifreq);
iwp->pointer = (void *) status + sizeof (struct ifreq);
result = dev->do_ioctl (dev, (struct ifreq *) status, flavor);
*count = ((sizeof (struct ifreq) + iwp->length)
/ sizeof (dev_status_t));
if (iwp->length % sizeof (dev_status_t))
(*count) ++;
}
else
{
*count = sizeof(struct ifreq) / sizeof(int);
result = dev->do_ioctl(dev, (struct ifreq *) status, flavor);
}
return result ? D_IO_ERROR : D_SUCCESS;
}
else
{
/* common get_status request */
return net_getstat (&((struct net_data *) d)->ifnet, flavor,
status, count);
}
}
static io_return_t
device_set_status(void *d, dev_flavor_t flavor, dev_status_t status,
mach_msg_type_number_t count)
{
if (flavor == NET_FLAGS)
{
if (count != 1)
return D_INVALID_SIZE;
short flags = status[0];
struct net_data *net = (struct net_data *) d;
dev_change_flags (net->dev, flags);
/* Change the flags of the Mach device, too. */
net->ifnet.if_flags = net->dev->flags;
return D_SUCCESS;
}
if(flavor < SIOCIWFIRST || flavor > SIOCIWLAST)
return D_INVALID_OPERATION;
if(! IW_IS_SET(flavor))
return D_INVALID_OPERATION;
if(count * sizeof(int) < sizeof(struct ifreq))
return D_INVALID_OPERATION;
struct net_data *nd = d;
struct linux_device *dev = nd->dev;
if(! dev->do_ioctl)
return D_INVALID_OPERATION;
if((flavor == SIOCSIWENCODE || flavor == SIOCSIWESSID
|| flavor == SIOCSIWNICKN || flavor == SIOCSIWSPY)
&& ((struct iwreq *) status)->u.data.pointer)
{
struct iw_point *iwp = &((struct iwreq *) status)->u.data;
/* safety check whether the status array is long enough ... */
if(count * sizeof(int) < sizeof(struct ifreq) + iwp->length)
return D_INVALID_OPERATION;
/* make sure, iwp->pointer points to the correct address */
if(iwp->pointer) iwp->pointer = (void *) status + sizeof(struct ifreq);
}
int result = dev->do_ioctl(dev, (struct ifreq *) status, flavor);
return result ? D_IO_ERROR : D_SUCCESS;
}
static io_return_t
device_set_filter (void *d, ipc_port_t port, int priority,
filter_t * filter, unsigned filter_count)
{
return net_set_filter (&((struct net_data *) d)->ifnet,
port, priority, filter, filter_count);
}
struct device_emulation_ops linux_net_emulation_ops =
{
NULL,
NULL,
dev_to_port,
device_open,
NULL,
device_write,
NULL,
NULL,
NULL,
device_set_status,
device_get_status,
device_set_filter,
NULL,
NULL,
NULL,
NULL
};
/* Do any initialization required for network devices. */
void
linux_net_emulation_init ()
{
skb_queue_head_init (&skb_done_list);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.