Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 1 | /* |
| 2 | * atari_nfeth.c - ARAnyM ethernet card driver for GNU/Linux |
| 3 | * |
| 4 | * Copyright (c) 2005 Milan Jurik, Petr Stehlik of ARAnyM dev team |
| 5 | * |
| 6 | * Based on ARAnyM driver for FreeMiNT written by Standa Opichal |
| 7 | * |
| 8 | * This software may be used and distributed according to the terms of |
| 9 | * the GNU General Public License (GPL), incorporated herein by reference. |
| 10 | */ |
| 11 | |
| 12 | #define DRV_VERSION "0.3" |
| 13 | #define DRV_RELDATE "10/12/2005" |
| 14 | |
| 15 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 16 | |
| 17 | #include <linux/netdevice.h> |
| 18 | #include <linux/etherdevice.h> |
Geert Uytterhoeven | ac75227 | 2011-06-11 14:58:17 -0700 | [diff] [blame] | 19 | #include <linux/interrupt.h> |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 20 | #include <linux/module.h> |
| 21 | #include <asm/natfeat.h> |
| 22 | #include <asm/virtconvert.h> |
| 23 | |
| 24 | enum { |
| 25 | GET_VERSION = 0,/* no parameters, return NFAPI_VERSION in d0 */ |
| 26 | XIF_INTLEVEL, /* no parameters, return Interrupt Level in d0 */ |
| 27 | XIF_IRQ, /* acknowledge interrupt from host */ |
| 28 | XIF_START, /* (ethX), called on 'ifup', start receiver thread */ |
| 29 | XIF_STOP, /* (ethX), called on 'ifdown', stop the thread */ |
| 30 | XIF_READLENGTH, /* (ethX), return size of network data block to read */ |
| 31 | XIF_READBLOCK, /* (ethX, buffer, size), read block of network data */ |
| 32 | XIF_WRITEBLOCK, /* (ethX, buffer, size), write block of network data */ |
| 33 | XIF_GET_MAC, /* (ethX, buffer, size), return MAC HW addr in buffer */ |
| 34 | XIF_GET_IPHOST, /* (ethX, buffer, size), return IP address of host */ |
| 35 | XIF_GET_IPATARI,/* (ethX, buffer, size), return IP address of atari */ |
| 36 | XIF_GET_NETMASK /* (ethX, buffer, size), return IP netmask */ |
| 37 | }; |
| 38 | |
| 39 | #define MAX_UNIT 8 |
| 40 | |
| 41 | /* These identify the driver base version and may not be removed. */ |
Greg Kroah-Hartman | b881bc4 | 2012-12-21 14:06:37 -0800 | [diff] [blame] | 42 | static const char version[] = |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 43 | KERN_INFO KBUILD_MODNAME ".c:v" DRV_VERSION " " DRV_RELDATE |
| 44 | " S.Opichal, M.Jurik, P.Stehlik\n" |
| 45 | KERN_INFO " http://aranym.org/\n"; |
| 46 | |
| 47 | MODULE_AUTHOR("Milan Jurik"); |
| 48 | MODULE_DESCRIPTION("Atari NFeth driver"); |
| 49 | MODULE_LICENSE("GPL"); |
| 50 | /* |
| 51 | MODULE_PARM(nfeth_debug, "i"); |
| 52 | MODULE_PARM_DESC(nfeth_debug, "nfeth_debug level (1-2)"); |
| 53 | */ |
| 54 | |
| 55 | |
| 56 | static long nfEtherID; |
| 57 | static int nfEtherIRQ; |
| 58 | |
| 59 | struct nfeth_private { |
| 60 | int ethX; |
| 61 | }; |
| 62 | |
| 63 | static struct net_device *nfeth_dev[MAX_UNIT]; |
| 64 | |
| 65 | static int nfeth_open(struct net_device *dev) |
| 66 | { |
| 67 | struct nfeth_private *priv = netdev_priv(dev); |
| 68 | int res; |
| 69 | |
| 70 | res = nf_call(nfEtherID + XIF_START, priv->ethX); |
| 71 | netdev_dbg(dev, "%s: %d\n", __func__, res); |
| 72 | |
| 73 | /* Ready for data */ |
| 74 | netif_start_queue(dev); |
| 75 | |
| 76 | return 0; |
| 77 | } |
| 78 | |
| 79 | static int nfeth_stop(struct net_device *dev) |
| 80 | { |
| 81 | struct nfeth_private *priv = netdev_priv(dev); |
| 82 | |
| 83 | /* No more data */ |
| 84 | netif_stop_queue(dev); |
| 85 | |
| 86 | nf_call(nfEtherID + XIF_STOP, priv->ethX); |
| 87 | |
| 88 | return 0; |
| 89 | } |
| 90 | |
| 91 | /* |
| 92 | * Read a packet out of the adapter and pass it to the upper layers |
| 93 | */ |
| 94 | static inline void recv_packet(struct net_device *dev) |
| 95 | { |
| 96 | struct nfeth_private *priv = netdev_priv(dev); |
| 97 | unsigned short pktlen; |
| 98 | struct sk_buff *skb; |
| 99 | |
| 100 | /* read packet length (excluding 32 bit crc) */ |
| 101 | pktlen = nf_call(nfEtherID + XIF_READLENGTH, priv->ethX); |
| 102 | |
| 103 | netdev_dbg(dev, "%s: %u\n", __func__, pktlen); |
| 104 | |
| 105 | if (!pktlen) { |
| 106 | netdev_dbg(dev, "%s: pktlen == 0\n", __func__); |
| 107 | dev->stats.rx_errors++; |
| 108 | return; |
| 109 | } |
| 110 | |
| 111 | skb = dev_alloc_skb(pktlen + 2); |
| 112 | if (!skb) { |
| 113 | netdev_dbg(dev, "%s: out of mem (buf_alloc failed)\n", |
| 114 | __func__); |
| 115 | dev->stats.rx_dropped++; |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | skb->dev = dev; |
| 120 | skb_reserve(skb, 2); /* 16 Byte align */ |
| 121 | skb_put(skb, pktlen); /* make room */ |
| 122 | nf_call(nfEtherID + XIF_READBLOCK, priv->ethX, virt_to_phys(skb->data), |
| 123 | pktlen); |
| 124 | |
| 125 | skb->protocol = eth_type_trans(skb, dev); |
| 126 | netif_rx(skb); |
| 127 | dev->last_rx = jiffies; |
| 128 | dev->stats.rx_packets++; |
| 129 | dev->stats.rx_bytes += pktlen; |
| 130 | |
| 131 | /* and enqueue packet */ |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | static irqreturn_t nfeth_interrupt(int irq, void *dev_id) |
| 136 | { |
| 137 | int i, m, mask; |
| 138 | |
| 139 | mask = nf_call(nfEtherID + XIF_IRQ, 0); |
| 140 | for (i = 0, m = 1; i < MAX_UNIT; m <<= 1, i++) { |
| 141 | if (mask & m && nfeth_dev[i]) { |
| 142 | recv_packet(nfeth_dev[i]); |
| 143 | nf_call(nfEtherID + XIF_IRQ, m); |
| 144 | } |
| 145 | } |
| 146 | return IRQ_HANDLED; |
| 147 | } |
| 148 | |
| 149 | static int nfeth_xmit(struct sk_buff *skb, struct net_device *dev) |
| 150 | { |
| 151 | unsigned int len; |
| 152 | char *data, shortpkt[ETH_ZLEN]; |
| 153 | struct nfeth_private *priv = netdev_priv(dev); |
| 154 | |
| 155 | data = skb->data; |
| 156 | len = skb->len; |
| 157 | if (len < ETH_ZLEN) { |
| 158 | memset(shortpkt, 0, ETH_ZLEN); |
| 159 | memcpy(shortpkt, data, len); |
| 160 | data = shortpkt; |
| 161 | len = ETH_ZLEN; |
| 162 | } |
| 163 | |
| 164 | netdev_dbg(dev, "%s: send %u bytes\n", __func__, len); |
| 165 | nf_call(nfEtherID + XIF_WRITEBLOCK, priv->ethX, virt_to_phys(data), |
| 166 | len); |
| 167 | |
| 168 | dev->stats.tx_packets++; |
| 169 | dev->stats.tx_bytes += len; |
| 170 | |
| 171 | dev_kfree_skb(skb); |
| 172 | return 0; |
| 173 | } |
| 174 | |
| 175 | static void nfeth_tx_timeout(struct net_device *dev) |
| 176 | { |
| 177 | dev->stats.tx_errors++; |
| 178 | netif_wake_queue(dev); |
| 179 | } |
| 180 | |
| 181 | static const struct net_device_ops nfeth_netdev_ops = { |
| 182 | .ndo_open = nfeth_open, |
| 183 | .ndo_stop = nfeth_stop, |
| 184 | .ndo_start_xmit = nfeth_xmit, |
| 185 | .ndo_tx_timeout = nfeth_tx_timeout, |
| 186 | .ndo_validate_addr = eth_validate_addr, |
| 187 | .ndo_change_mtu = eth_change_mtu, |
| 188 | .ndo_set_mac_address = eth_mac_addr, |
| 189 | }; |
| 190 | |
| 191 | static struct net_device * __init nfeth_probe(int unit) |
| 192 | { |
| 193 | struct net_device *dev; |
| 194 | struct nfeth_private *priv; |
| 195 | char mac[ETH_ALEN], host_ip[32], local_ip[32]; |
| 196 | int err; |
| 197 | |
Geert Uytterhoeven | 5549005 | 2013-08-14 10:45:00 +0200 | [diff] [blame] | 198 | if (!nf_call(nfEtherID + XIF_GET_MAC, unit, virt_to_phys(mac), |
| 199 | ETH_ALEN)) |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 200 | return NULL; |
| 201 | |
| 202 | dev = alloc_etherdev(sizeof(struct nfeth_private)); |
| 203 | if (!dev) |
| 204 | return NULL; |
| 205 | |
| 206 | dev->irq = nfEtherIRQ; |
| 207 | dev->netdev_ops = &nfeth_netdev_ops; |
| 208 | |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 209 | memcpy(dev->dev_addr, mac, ETH_ALEN); |
| 210 | |
| 211 | priv = netdev_priv(dev); |
| 212 | priv->ethX = unit; |
| 213 | |
| 214 | err = register_netdev(dev); |
| 215 | if (err) { |
| 216 | free_netdev(dev); |
| 217 | return NULL; |
| 218 | } |
| 219 | |
| 220 | nf_call(nfEtherID + XIF_GET_IPHOST, unit, |
Geert Uytterhoeven | 5549005 | 2013-08-14 10:45:00 +0200 | [diff] [blame] | 221 | virt_to_phys(host_ip), sizeof(host_ip)); |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 222 | nf_call(nfEtherID + XIF_GET_IPATARI, unit, |
Geert Uytterhoeven | 5549005 | 2013-08-14 10:45:00 +0200 | [diff] [blame] | 223 | virt_to_phys(local_ip), sizeof(local_ip)); |
Milan Jurik | 9cd7b14 | 2008-11-18 21:02:18 +0100 | [diff] [blame] | 224 | |
| 225 | netdev_info(dev, KBUILD_MODNAME " addr:%s (%s) HWaddr:%pM\n", host_ip, |
| 226 | local_ip, mac); |
| 227 | |
| 228 | return dev; |
| 229 | } |
| 230 | |
| 231 | static int __init nfeth_init(void) |
| 232 | { |
| 233 | long ver; |
| 234 | int error, i; |
| 235 | |
| 236 | nfEtherID = nf_get_id("ETHERNET"); |
| 237 | if (!nfEtherID) |
| 238 | return -ENODEV; |
| 239 | |
| 240 | ver = nf_call(nfEtherID + GET_VERSION); |
| 241 | pr_info("API %lu\n", ver); |
| 242 | |
| 243 | nfEtherIRQ = nf_call(nfEtherID + XIF_INTLEVEL); |
| 244 | error = request_irq(nfEtherIRQ, nfeth_interrupt, IRQF_SHARED, |
| 245 | "eth emu", nfeth_interrupt); |
| 246 | if (error) { |
| 247 | pr_err("request for irq %d failed %d", nfEtherIRQ, error); |
| 248 | return error; |
| 249 | } |
| 250 | |
| 251 | for (i = 0; i < MAX_UNIT; i++) |
| 252 | nfeth_dev[i] = nfeth_probe(i); |
| 253 | |
| 254 | return 0; |
| 255 | } |
| 256 | |
| 257 | static void __exit nfeth_cleanup(void) |
| 258 | { |
| 259 | int i; |
| 260 | |
| 261 | for (i = 0; i < MAX_UNIT; i++) { |
| 262 | if (nfeth_dev[i]) { |
| 263 | unregister_netdev(nfeth_dev[0]); |
| 264 | free_netdev(nfeth_dev[0]); |
| 265 | } |
| 266 | } |
| 267 | free_irq(nfEtherIRQ, nfeth_interrupt); |
| 268 | } |
| 269 | |
| 270 | module_init(nfeth_init); |
| 271 | module_exit(nfeth_cleanup); |