blob: b11fdc17e77c77ffe6c3f833e8ffce094f426736 [file] [log] [blame]
/*
* Bogus Network Driver for PowerPC Full System Simulator
*
* (C) Copyright IBM Corporation 2003-2005
*
* Bogus Network Driver
*
* Author: JimiX <jimix@watson.ibm.com>
* Maintained By: Eric Van Hensbergen <ericvh@gmail.com>
*
* inspired by drivers/net/ibmveth.c
* written by Dave Larson
*
* Some code is from the IBM Full System Simulator Group in ARL
* Author: Patrick Bohrer <IBM Austin Research Lab>
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/in.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/if_ether.h> /* For the statistics structure. */
#include <linux/if_arp.h> /* For ARPHRD_ETHER */
#include <linux/workqueue.h>
#include <asm/prom.h>
#include <linux/version.h>
#include <asm/systemsim.h>
#define SYSTEMSIM_NET_PROBE 119
#define SYSTEMSIM_NET_SEND 120
#define SYSTEMSIM_NET_RECV 121
static inline int systemsim_bogusnet_probe(int devno, void *buf)
{
return callthru2(SYSTEMSIM_NET_PROBE,
(unsigned long)devno, (unsigned long)buf);
}
static inline int systemsim_bogusnet_send(int devno, void *buf, ulong size)
{
return callthru3(SYSTEMSIM_NET_SEND,
(unsigned long)devno,
(unsigned long)buf, (unsigned long)size);
}
static inline int systemsim_bogusnet_recv(int devno, void *buf, ulong size)
{
return callthru3(SYSTEMSIM_NET_RECV,
(unsigned long)devno,
(unsigned long)buf, (unsigned long)size);
}
static irqreturn_t
systemsim_net_intr(int irq, void *dev_instance);
#define MAMBO_MTU 1500
struct netdev_private {
struct delayed_work poll_task;
int devno;
int closing;
void *data;
struct net_device_stats stats;
};
static int systemsim_net_probedev(int devno, void *buf)
{
struct device_node *systemsim;
struct device_node *net;
unsigned int *reg;
systemsim = find_path_device("/systemsim");
if (systemsim == NULL) {
return -1;
}
net = find_path_device("/systemsim/bogus-net@0");
if (net == NULL) {
return -1;
}
reg = (unsigned int *)get_property(net, "reg", 0);
if (*reg != devno) {
return -1;
}
return systemsim_bogusnet_probe(devno, buf);
}
static int systemsim_net_send(int devno, void *buf, ulong size)
{
return systemsim_bogusnet_send(devno, buf, size);
}
static int systemsim_net_recv(int devno, void *buf, ulong size)
{
return systemsim_bogusnet_recv(devno, buf, size);
}
static int systemsim_net_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct netdev_private *priv = (struct netdev_private *)dev->priv;
int devno = priv->devno;
skb->dev = dev;
/* we might need to checksum or something */
systemsim_net_send(devno, skb->data, skb->len);
dev->last_rx = jiffies;
priv->stats.rx_bytes += skb->len;
priv->stats.tx_bytes += skb->len;
priv->stats.rx_packets++;
priv->stats.tx_packets++;
dev_kfree_skb(skb);
return (0);
}
static int systemsim_net_poll(struct net_device *dev, int *budget)
{
struct netdev_private *np = dev->priv;
int devno = np->devno;
char buffer[1600];
int ns;
struct sk_buff *skb;
int frames = 0;
int max_frames = min(*budget, dev->quota);
int ret = 0;
while ((ns = systemsim_net_recv(devno, buffer, 1600)) > 0) {
if ((skb = dev_alloc_skb(ns + 2)) != NULL) {
skb->dev = dev;
skb_reserve(skb, 2); /* 16 byte align the IP
* header */
#ifdef HAS_IP_COPYSUM
eth_copy_and_sum(skb, buffer, ns, 0);
skb_put(skb, ns);
#else
memcpy(skb_put(skb, ns), buffer, ns);
#endif
skb->protocol = eth_type_trans(skb, dev);
if (dev->irq)
netif_receive_skb(skb);
else
netif_rx(skb);
dev->last_rx = jiffies;
np->stats.rx_packets++;
np->stats.rx_bytes += ns;
} else {
printk("Failed to allocated skbuff, "
"dropping packet\n");
np->stats.rx_dropped++;
/* wait for another cycle */
return 1;
}
++frames;
if (frames > max_frames) {
ret = 1;
break;
}
}
*budget -= frames;
dev->quota -= frames;
if ((!ret) && (dev->irq))
netif_rx_complete(dev);
return ret;
}
static void systemsim_net_timer(struct work_struct *ptp)
{
int budget = 16;
struct delayed_work *rptp = (struct delayed_work *)ptp;
struct netdev_private *priv = (struct netdev_private *) container_of(rptp, struct netdev_private, poll_task);
struct net_device *dev = (struct net_device *) priv->data;
systemsim_net_poll(dev, &budget);
if (!priv->closing) {
schedule_delayed_work(&priv->poll_task, 1);
}
}
static struct net_device_stats *get_stats(struct net_device *dev)
{
struct netdev_private *priv = (struct netdev_private *)dev->priv;
return (struct net_device_stats *)&(priv->stats);
}
static irqreturn_t
systemsim_net_intr(int irq, void *dev_instance)
{
struct net_device *dev = dev_instance;
if (netif_rx_schedule_prep(dev)) {
__netif_rx_schedule(dev);
}
return IRQ_HANDLED;
}
static int systemsim_net_open(struct net_device *dev)
{
struct netdev_private *priv;
int ret = 0;
priv = dev->priv;
/*
* we can't start polling in systemsim_net_init, because I don't think
* workqueues are usable that early. so start polling now.
*/
if (dev->irq) {
ret = request_irq(dev->irq, &systemsim_net_intr, 0,
dev->name, dev);
if (ret == 0) {
netif_start_queue(dev);
} else {
printk(KERN_ERR "systemsim net: request irq failed\n");
}
/* probe with NULL to activate interrupts */
systemsim_bogusnet_probe(priv->devno, NULL);
} else {
systemsim_net_timer((struct work_struct *)&priv->poll_task);
}
return ret;
}
static int systemsim_net_close(struct net_device *dev)
{
struct netdev_private *priv;
netif_stop_queue(dev);
if (dev->irq)
free_irq(dev->irq, dev);
priv = dev->priv;
priv->closing = 1;
if (dev->irq == 0) {
cancel_delayed_work(&priv->poll_task);
flush_scheduled_work();
}
return 0;
}
static struct net_device_stats systemsim_net_stats;
static struct net_device_stats *systemsim_net_get_stats(struct net_device *dev)
{
return &systemsim_net_stats;
}
static int systemsim_net_set_mac_address(struct net_device *dev, void *p)
{
return -EOPNOTSUPP;
}
static int systemsim_net_ioctl(struct net_device *dev, struct ifreq *ifr,
int cmd)
{
return -EOPNOTSUPP;
}
static int nextdevno = 0; /* running count of device numbers */
/* Initialize the rest of the device. */
int __init do_systemsim_net_probe(struct net_device *dev)
{
struct netdev_private *priv;
int devno = nextdevno++;
int irq;
printk("eth%d: bogus network driver initialization\n", devno);
irq = systemsim_net_probedev(devno, dev->dev_addr);
if (irq < 0) {
printk("No IRQ retreived\n");
return (-ENODEV);
}
printk("%s: %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n", dev->name,
dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
SET_MODULE_OWNER(dev);
dev->irq = irq;
dev->mtu = MAMBO_MTU;
dev->open = systemsim_net_open;
dev->poll = systemsim_net_poll;
dev->weight = 16;
dev->stop = systemsim_net_close;
dev->hard_start_xmit = systemsim_net_start_xmit;
dev->get_stats = systemsim_net_get_stats;
dev->set_mac_address = systemsim_net_set_mac_address;
dev->do_ioctl = systemsim_net_ioctl;
dev->priv = kmalloc(sizeof(struct netdev_private), GFP_KERNEL);
if (dev->priv == NULL)
return -ENOMEM;
memset(dev->priv, 0, sizeof(struct netdev_private));
priv = dev->priv;
priv->devno = devno;
priv->closing = 0;
dev->get_stats = get_stats;
if (dev->irq == 0) {
priv->data = (void *)dev;
INIT_DELAYED_WORK(&priv->poll_task, systemsim_net_timer);
}
return (0);
};
struct net_device *__init systemsim_net_probe(int unit)
{
struct net_device *dev = alloc_etherdev(0);
int err;
if (!dev)
return ERR_PTR(-ENODEV);
sprintf(dev->name, "eth%d", unit);
netdev_boot_setup_check(dev);
err = do_systemsim_net_probe(dev);
if (err)
goto out;
err = register_netdev(dev);
if (err)
goto out;
return dev;
out:
free_netdev(dev);
return ERR_PTR(err);
}
int __init init_systemsim_net(void)
{
systemsim_net_probe(0);
return 0;
}
module_init(init_systemsim_net);
MODULE_DESCRIPTION("Systemsim Network Driver");
MODULE_LICENSE("GPL");