|  | /* | 
|  | * atari_nfeth.c - ARAnyM ethernet card driver for GNU/Linux | 
|  | * | 
|  | * Copyright (c) 2005 Milan Jurik, Petr Stehlik of ARAnyM dev team | 
|  | * | 
|  | * Based on ARAnyM driver for FreeMiNT written by Standa Opichal | 
|  | * | 
|  | * This software may be used and distributed according to the terms of | 
|  | * the GNU General Public License (GPL), incorporated herein by reference. | 
|  | */ | 
|  |  | 
|  | #define DRV_VERSION	"0.3" | 
|  | #define DRV_RELDATE	"10/12/2005" | 
|  |  | 
|  | #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt | 
|  |  | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/etherdevice.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/module.h> | 
|  | #include <asm/natfeat.h> | 
|  | #include <asm/virtconvert.h> | 
|  |  | 
|  | enum { | 
|  | GET_VERSION = 0,/* no parameters, return NFAPI_VERSION in d0 */ | 
|  | XIF_INTLEVEL,	/* no parameters, return Interrupt Level in d0 */ | 
|  | XIF_IRQ,	/* acknowledge interrupt from host */ | 
|  | XIF_START,	/* (ethX), called on 'ifup', start receiver thread */ | 
|  | XIF_STOP,	/* (ethX), called on 'ifdown', stop the thread */ | 
|  | XIF_READLENGTH,	/* (ethX), return size of network data block to read */ | 
|  | XIF_READBLOCK,	/* (ethX, buffer, size), read block of network data */ | 
|  | XIF_WRITEBLOCK,	/* (ethX, buffer, size), write block of network data */ | 
|  | XIF_GET_MAC,	/* (ethX, buffer, size), return MAC HW addr in buffer */ | 
|  | XIF_GET_IPHOST,	/* (ethX, buffer, size), return IP address of host */ | 
|  | XIF_GET_IPATARI,/* (ethX, buffer, size), return IP address of atari */ | 
|  | XIF_GET_NETMASK	/* (ethX, buffer, size), return IP netmask */ | 
|  | }; | 
|  |  | 
|  | #define MAX_UNIT	8 | 
|  |  | 
|  | /* These identify the driver base version and may not be removed. */ | 
|  | static const char version[] __devinitconst = | 
|  | KERN_INFO KBUILD_MODNAME ".c:v" DRV_VERSION " " DRV_RELDATE | 
|  | " S.Opichal, M.Jurik, P.Stehlik\n" | 
|  | KERN_INFO " http://aranym.org/\n"; | 
|  |  | 
|  | MODULE_AUTHOR("Milan Jurik"); | 
|  | MODULE_DESCRIPTION("Atari NFeth driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | /* | 
|  | MODULE_PARM(nfeth_debug, "i"); | 
|  | MODULE_PARM_DESC(nfeth_debug, "nfeth_debug level (1-2)"); | 
|  | */ | 
|  |  | 
|  |  | 
|  | static long nfEtherID; | 
|  | static int nfEtherIRQ; | 
|  |  | 
|  | struct nfeth_private { | 
|  | int ethX; | 
|  | }; | 
|  |  | 
|  | static struct net_device *nfeth_dev[MAX_UNIT]; | 
|  |  | 
|  | static int nfeth_open(struct net_device *dev) | 
|  | { | 
|  | struct nfeth_private *priv = netdev_priv(dev); | 
|  | int res; | 
|  |  | 
|  | res = nf_call(nfEtherID + XIF_START, priv->ethX); | 
|  | netdev_dbg(dev, "%s: %d\n", __func__, res); | 
|  |  | 
|  | /* Ready for data */ | 
|  | netif_start_queue(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int nfeth_stop(struct net_device *dev) | 
|  | { | 
|  | struct nfeth_private *priv = netdev_priv(dev); | 
|  |  | 
|  | /* No more data */ | 
|  | netif_stop_queue(dev); | 
|  |  | 
|  | nf_call(nfEtherID + XIF_STOP, priv->ethX); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Read a packet out of the adapter and pass it to the upper layers | 
|  | */ | 
|  | static inline void recv_packet(struct net_device *dev) | 
|  | { | 
|  | struct nfeth_private *priv = netdev_priv(dev); | 
|  | unsigned short pktlen; | 
|  | struct sk_buff *skb; | 
|  |  | 
|  | /* read packet length (excluding 32 bit crc) */ | 
|  | pktlen = nf_call(nfEtherID + XIF_READLENGTH, priv->ethX); | 
|  |  | 
|  | netdev_dbg(dev, "%s: %u\n", __func__, pktlen); | 
|  |  | 
|  | if (!pktlen) { | 
|  | netdev_dbg(dev, "%s: pktlen == 0\n", __func__); | 
|  | dev->stats.rx_errors++; | 
|  | return; | 
|  | } | 
|  |  | 
|  | skb = dev_alloc_skb(pktlen + 2); | 
|  | if (!skb) { | 
|  | netdev_dbg(dev, "%s: out of mem (buf_alloc failed)\n", | 
|  | __func__); | 
|  | dev->stats.rx_dropped++; | 
|  | return; | 
|  | } | 
|  |  | 
|  | skb->dev = dev; | 
|  | skb_reserve(skb, 2);		/* 16 Byte align  */ | 
|  | skb_put(skb, pktlen);		/* make room */ | 
|  | nf_call(nfEtherID + XIF_READBLOCK, priv->ethX, virt_to_phys(skb->data), | 
|  | pktlen); | 
|  |  | 
|  | skb->protocol = eth_type_trans(skb, dev); | 
|  | netif_rx(skb); | 
|  | dev->last_rx = jiffies; | 
|  | dev->stats.rx_packets++; | 
|  | dev->stats.rx_bytes += pktlen; | 
|  |  | 
|  | /* and enqueue packet */ | 
|  | return; | 
|  | } | 
|  |  | 
|  | static irqreturn_t nfeth_interrupt(int irq, void *dev_id) | 
|  | { | 
|  | int i, m, mask; | 
|  |  | 
|  | mask = nf_call(nfEtherID + XIF_IRQ, 0); | 
|  | for (i = 0, m = 1; i < MAX_UNIT; m <<= 1, i++) { | 
|  | if (mask & m && nfeth_dev[i]) { | 
|  | recv_packet(nfeth_dev[i]); | 
|  | nf_call(nfEtherID + XIF_IRQ, m); | 
|  | } | 
|  | } | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int nfeth_xmit(struct sk_buff *skb, struct net_device *dev) | 
|  | { | 
|  | unsigned int len; | 
|  | char *data, shortpkt[ETH_ZLEN]; | 
|  | struct nfeth_private *priv = netdev_priv(dev); | 
|  |  | 
|  | data = skb->data; | 
|  | len = skb->len; | 
|  | if (len < ETH_ZLEN) { | 
|  | memset(shortpkt, 0, ETH_ZLEN); | 
|  | memcpy(shortpkt, data, len); | 
|  | data = shortpkt; | 
|  | len = ETH_ZLEN; | 
|  | } | 
|  |  | 
|  | netdev_dbg(dev, "%s: send %u bytes\n", __func__, len); | 
|  | nf_call(nfEtherID + XIF_WRITEBLOCK, priv->ethX, virt_to_phys(data), | 
|  | len); | 
|  |  | 
|  | dev->stats.tx_packets++; | 
|  | dev->stats.tx_bytes += len; | 
|  |  | 
|  | dev_kfree_skb(skb); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void nfeth_tx_timeout(struct net_device *dev) | 
|  | { | 
|  | dev->stats.tx_errors++; | 
|  | netif_wake_queue(dev); | 
|  | } | 
|  |  | 
|  | static const struct net_device_ops nfeth_netdev_ops = { | 
|  | .ndo_open		= nfeth_open, | 
|  | .ndo_stop		= nfeth_stop, | 
|  | .ndo_start_xmit		= nfeth_xmit, | 
|  | .ndo_tx_timeout		= nfeth_tx_timeout, | 
|  | .ndo_validate_addr	= eth_validate_addr, | 
|  | .ndo_change_mtu		= eth_change_mtu, | 
|  | .ndo_set_mac_address	= eth_mac_addr, | 
|  | }; | 
|  |  | 
|  | static struct net_device * __init nfeth_probe(int unit) | 
|  | { | 
|  | struct net_device *dev; | 
|  | struct nfeth_private *priv; | 
|  | char mac[ETH_ALEN], host_ip[32], local_ip[32]; | 
|  | int err; | 
|  |  | 
|  | if (!nf_call(nfEtherID + XIF_GET_MAC, unit, mac, ETH_ALEN)) | 
|  | return NULL; | 
|  |  | 
|  | dev = alloc_etherdev(sizeof(struct nfeth_private)); | 
|  | if (!dev) | 
|  | return NULL; | 
|  |  | 
|  | dev->irq = nfEtherIRQ; | 
|  | dev->netdev_ops = &nfeth_netdev_ops; | 
|  |  | 
|  | memcpy(dev->dev_addr, mac, ETH_ALEN); | 
|  |  | 
|  | priv = netdev_priv(dev); | 
|  | priv->ethX = unit; | 
|  |  | 
|  | err = register_netdev(dev); | 
|  | if (err) { | 
|  | free_netdev(dev); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | nf_call(nfEtherID + XIF_GET_IPHOST, unit, | 
|  | host_ip, sizeof(host_ip)); | 
|  | nf_call(nfEtherID + XIF_GET_IPATARI, unit, | 
|  | local_ip, sizeof(local_ip)); | 
|  |  | 
|  | netdev_info(dev, KBUILD_MODNAME " addr:%s (%s) HWaddr:%pM\n", host_ip, | 
|  | local_ip, mac); | 
|  |  | 
|  | return dev; | 
|  | } | 
|  |  | 
|  | static int __init nfeth_init(void) | 
|  | { | 
|  | long ver; | 
|  | int error, i; | 
|  |  | 
|  | nfEtherID = nf_get_id("ETHERNET"); | 
|  | if (!nfEtherID) | 
|  | return -ENODEV; | 
|  |  | 
|  | ver = nf_call(nfEtherID + GET_VERSION); | 
|  | pr_info("API %lu\n", ver); | 
|  |  | 
|  | nfEtherIRQ = nf_call(nfEtherID + XIF_INTLEVEL); | 
|  | error = request_irq(nfEtherIRQ, nfeth_interrupt, IRQF_SHARED, | 
|  | "eth emu", nfeth_interrupt); | 
|  | if (error) { | 
|  | pr_err("request for irq %d failed %d", nfEtherIRQ, error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < MAX_UNIT; i++) | 
|  | nfeth_dev[i] = nfeth_probe(i); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit nfeth_cleanup(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < MAX_UNIT; i++) { | 
|  | if (nfeth_dev[i]) { | 
|  | unregister_netdev(nfeth_dev[0]); | 
|  | free_netdev(nfeth_dev[0]); | 
|  | } | 
|  | } | 
|  | free_irq(nfEtherIRQ, nfeth_interrupt); | 
|  | } | 
|  |  | 
|  | module_init(nfeth_init); | 
|  | module_exit(nfeth_cleanup); |