aboutsummaryrefslogtreecommitdiff
path: root/pfinet/ethernet.c
diff options
context:
space:
mode:
Diffstat (limited to 'pfinet/ethernet.c')
-rw-r--r--pfinet/ethernet.c383
1 files changed, 383 insertions, 0 deletions
diff --git a/pfinet/ethernet.c b/pfinet/ethernet.c
new file mode 100644
index 00000000..053fd1be
--- /dev/null
+++ b/pfinet/ethernet.c
@@ -0,0 +1,383 @@
+/*
+ Copyright (C) 1995, 1996, 1998, 1999, 2000, 2002, 2007
+ Free Software Foundation, Inc.
+
+ Written by Michael I. Bushnell, p/BSG.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd 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.
+
+ The GNU Hurd 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, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+/* Do not include glue-include/linux/errno.h */
+#define _HACK_ERRNO_H
+#include "pfinet.h"
+
+#include <device/device.h>
+#include <device/net_status.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <error.h>
+#include <fcntl.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+
+
+struct port_class *etherreadclass;
+
+struct ether_device
+{
+ struct ether_device *next;
+ device_t ether_port;
+ struct port_info *readpt;
+ mach_port_t readptname;
+ struct device dev;
+};
+
+/* Linked list of all ethernet devices. */
+struct ether_device *ether_dev;
+
+struct enet_statistics retbuf;
+
+
+/* Mach doesn't provide this. DAMN. */
+struct enet_statistics *
+ethernet_get_stats (struct device *dev)
+{
+ return &retbuf;
+}
+
+int
+ethernet_stop (struct device *dev)
+{
+ return 0;
+}
+
+void
+ethernet_set_multi (struct device *dev)
+{
+}
+
+static short ether_filter[] =
+{
+#ifdef NETF_IN
+ /* We have to tell the packet filtering code that we're interested in
+ incoming packets. */
+ NETF_IN, /* Header. */
+#endif
+ NETF_PUSHLIT | NETF_NOP,
+ 1
+};
+static int ether_filter_len = sizeof (ether_filter) / sizeof (short);
+
+static struct bpf_insn bpf_ether_filter[] =
+{
+ {NETF_IN|NETF_BPF, 0, 0, 0}, /* Header. */
+ {BPF_LD|BPF_H|BPF_ABS, 0, 0, 12}, /* Load Ethernet type */
+ {BPF_JMP|BPF_JEQ|BPF_K, 2, 0, 0x0806}, /* Accept ARP */
+ {BPF_JMP|BPF_JEQ|BPF_K, 1, 0, 0x0800}, /* Accept IPv4 */
+ {BPF_JMP|BPF_JEQ|BPF_K, 0, 1, 0x86DD}, /* Accept IPv6 */
+ {BPF_RET|BPF_K, 0, 0, 1500}, /* And return 1500 bytes */
+ {BPF_RET|BPF_K, 0, 0, 0}, /* Or discard it all */
+};
+static int bpf_ether_filter_len = sizeof (bpf_ether_filter) / sizeof (short);
+
+static struct port_bucket *etherport_bucket;
+
+
+static void *
+ethernet_thread (void *arg)
+{
+ ports_manage_port_operations_one_thread (etherport_bucket,
+ ethernet_demuxer,
+ 0);
+ return NULL;
+}
+
+int
+ethernet_demuxer (mach_msg_header_t *inp,
+ mach_msg_header_t *outp)
+{
+ struct net_rcv_msg *msg = (struct net_rcv_msg *) inp;
+ struct sk_buff *skb;
+ int datalen;
+ struct ether_device *edev;
+ struct device *dev = 0;
+
+ if (inp->msgh_id != NET_RCV_MSG_ID)
+ return 0;
+
+ for (edev = ether_dev; edev; edev = edev->next)
+ if (inp->msgh_local_port == edev->readptname)
+ dev = &edev->dev;
+
+ if (! dev)
+ {
+ if (inp->msgh_remote_port != MACH_PORT_NULL)
+ mach_port_deallocate (mach_task_self (), inp->msgh_remote_port);
+ return 1;
+ }
+
+ datalen = ETH_HLEN
+ + msg->packet_type.msgt_number - sizeof (struct packet_header);
+
+ pthread_mutex_lock (&net_bh_lock);
+ skb = alloc_skb (datalen, GFP_ATOMIC);
+ skb_put (skb, datalen);
+ skb->dev = dev;
+
+ /* Copy the two parts of the frame into the buffer. */
+ bcopy (msg->header, skb->data, ETH_HLEN);
+ bcopy (msg->packet + sizeof (struct packet_header),
+ skb->data + ETH_HLEN,
+ datalen - ETH_HLEN);
+
+ /* Drop it on the queue. */
+ skb->protocol = eth_type_trans (skb, dev);
+ netif_rx (skb);
+ pthread_mutex_unlock (&net_bh_lock);
+
+ return 1;
+}
+
+
+void
+ethernet_initialize (void)
+{
+ pthread_t thread;
+ error_t err;
+ etherport_bucket = ports_create_bucket ();
+ etherreadclass = ports_create_class (0, 0);
+
+ err = pthread_create (&thread, NULL, ethernet_thread, NULL);
+ if (!err)
+ pthread_detach (thread);
+ else
+ {
+ errno = err;
+ perror ("pthread_create");
+ }
+}
+
+int
+ethernet_open (struct device *dev)
+{
+ error_t err;
+ device_t master_device;
+ struct ether_device *edev = (struct ether_device *) dev->priv;
+
+ assert (edev->ether_port == MACH_PORT_NULL);
+
+ err = ports_create_port (etherreadclass, etherport_bucket,
+ sizeof (struct port_info), &edev->readpt);
+ assert_perror (err);
+ edev->readptname = ports_get_right (edev->readpt);
+ mach_port_insert_right (mach_task_self (), edev->readptname, edev->readptname,
+ MACH_MSG_TYPE_MAKE_SEND);
+
+ mach_port_set_qlimit (mach_task_self (), edev->readptname, MACH_PORT_QLIMIT_MAX);
+
+ master_device = file_name_lookup (dev->name, O_READ | O_WRITE, 0);
+ if (master_device != MACH_PORT_NULL)
+ {
+ /* The device name here is the path of a device file. */
+ err = device_open (master_device, D_WRITE | D_READ, "eth", &edev->ether_port);
+ mach_port_deallocate (mach_task_self (), master_device);
+ if (err)
+ error (2, err, "device_open on %s", dev->name);
+
+ err = device_set_filter (edev->ether_port, ports_get_right (edev->readpt),
+ MACH_MSG_TYPE_MAKE_SEND, 0,
+ bpf_ether_filter, bpf_ether_filter_len);
+ if (err)
+ error (2, err, "device_set_filter on %s", dev->name);
+ }
+ else
+ {
+ /* No, perhaps a Mach device? */
+ int file_errno = errno;
+ err = get_privileged_ports (0, &master_device);
+ if (err)
+ {
+ error (0, file_errno, "file_name_lookup %s", dev->name);
+ error (2, err, "and cannot get device master port");
+ }
+ err = device_open (master_device, D_WRITE | D_READ, dev->name, &edev->ether_port);
+ mach_port_deallocate (mach_task_self (), master_device);
+ if (err)
+ {
+ error (0, file_errno, "file_name_lookup %s", dev->name);
+ error (2, err, "device_open(%s)", dev->name);
+ }
+
+ err = device_set_filter (edev->ether_port, ports_get_right (edev->readpt),
+ MACH_MSG_TYPE_MAKE_SEND, 0,
+ ether_filter, ether_filter_len);
+ if (err)
+ error (2, err, "device_set_filter on %s", dev->name);
+ }
+
+ return 0;
+}
+
+int
+ethernet_close (struct device *dev)
+{
+ struct ether_device *edev = (struct ether_device *) dev->priv;
+
+ mach_port_deallocate (mach_task_self (), edev->readptname);
+ edev->readptname = MACH_PORT_NULL;
+ ports_destroy_right (edev->readpt);
+ edev->readpt = NULL;
+ device_close (edev->ether_port);
+ mach_port_deallocate (mach_task_self (), edev->ether_port);
+ edev->ether_port = MACH_PORT_NULL;
+}
+
+/* Transmit an ethernet frame */
+int
+ethernet_xmit (struct sk_buff *skb, struct device *dev)
+{
+ error_t err;
+ struct ether_device *edev = (struct ether_device *) dev->priv;
+ u_int count;
+ u_int tried = 0;
+
+ do
+ {
+ tried++;
+ err = device_write (edev->ether_port, D_NOWAIT, 0, skb->data, skb->len, &count);
+ if (err == EMACH_SEND_INVALID_DEST || err == EMIG_SERVER_DIED)
+ {
+ /* Device probably just died, try to reopen it. */
+
+ if (tried == 2)
+ /* Too many tries, abort */
+ break;
+
+ ethernet_close (dev);
+ ethernet_open (dev);
+ }
+ else
+ {
+ assert_perror (err);
+ assert (count == skb->len);
+ }
+ }
+ while (err);
+
+ dev_kfree_skb (skb);
+ return 0;
+}
+
+/* Set device flags (e.g. promiscuous) */
+static int
+ethernet_change_flags (struct device *dev, short flags)
+{
+ error_t err = 0;
+#ifdef NET_FLAGS
+ int status = flags;
+ struct ether_device *edev = (struct ether_device *) dev->priv;
+ err = device_set_status (edev->ether_port, NET_FLAGS, &status, 1);
+ if (err == D_INVALID_OPERATION)
+ /* Not supported, ignore. */
+ err = 0;
+#endif
+ return err;
+}
+
+void
+setup_ethernet_device (char *name, struct device **device)
+{
+ struct net_status netstat;
+ size_t count;
+ int net_address[2];
+ error_t err;
+ struct ether_device *edev;
+ struct device *dev;
+
+ edev = calloc (1, sizeof (struct ether_device));
+ if (!edev)
+ error (2, ENOMEM, "%s", name);
+ edev->next = ether_dev;
+ ether_dev = edev;
+
+ *device = dev = &edev->dev;
+
+ dev->name = strdup (name);
+ /* Functions. These ones are the true "hardware layer" in Linux. */
+ dev->open = 0; /* We set up before calling dev_open. */
+ dev->stop = ethernet_stop;
+ dev->hard_start_xmit = ethernet_xmit;
+ dev->get_stats = ethernet_get_stats;
+ dev->set_multicast_list = ethernet_set_multi;
+
+ /* These are the ones set by drivers/net/net_init.c::ether_setup. */
+ dev->hard_header = eth_header;
+ dev->rebuild_header = eth_rebuild_header;
+ dev->hard_header_cache = eth_header_cache;
+ dev->header_cache_update = eth_header_cache_update;
+ dev->hard_header_parse = eth_header_parse;
+ /* We can't do these two (and we never try anyway). */
+ /* dev->change_mtu = eth_change_mtu; */
+ /* dev->set_mac_address = eth_mac_addr; */
+
+ /* Some more fields */
+ dev->priv = edev; /* For reverse lookup. */
+ dev->type = ARPHRD_ETHER;
+ dev->hard_header_len = ETH_HLEN;
+ dev->addr_len = ETH_ALEN;
+ memset (dev->broadcast, 0xff, ETH_ALEN);
+ dev->flags = IFF_BROADCAST | IFF_MULTICAST;
+
+ /* FIXME: Receive all multicast to fix IPv6, until we implement
+ ethernet_set_multi. */
+ dev->flags |= IFF_ALLMULTI;
+
+ dev->change_flags = ethernet_change_flags;
+
+ dev_init_buffers (dev);
+
+ ethernet_open (dev);
+
+ /* Fetch hardware information */
+ count = NET_STATUS_COUNT;
+ err = device_get_status (edev->ether_port, NET_STATUS,
+ (dev_status_t) &netstat, &count);
+ if (err)
+ error (2, err, "%s: Cannot get device status", name);
+ dev->mtu = netstat.max_packet_size - dev->hard_header_len;
+ assert (netstat.header_format == HDR_ETHERNET);
+ assert (netstat.header_size == ETH_HLEN);
+ assert (netstat.address_size == ETH_ALEN);
+
+ count = 2;
+ assert (count * sizeof (int) >= ETH_ALEN);
+ err = device_get_status (edev->ether_port, NET_ADDRESS, net_address, &count);
+ if (err)
+ error (2, err, "%s: Cannot get hardware Ethernet address", name);
+ net_address[0] = ntohl (net_address[0]);
+ net_address[1] = ntohl (net_address[1]);
+ bcopy (net_address, dev->dev_addr, ETH_ALEN);
+
+ /* That should be enough. */
+
+ /* This call adds the device to the `dev_base' chain,
+ initializes its `ifindex' member (which matters!),
+ and tells the protocol stacks about the device. */
+ err = - register_netdevice (dev);
+ assert_perror (err);
+}