/***************************************************************************/ /* */ /* Copyright (c) 1999-2000 by Atmel Corporation */ /* */ /* This software is copyrighted by and is the sole property of Atmel */ /* Corporation. All rights, title, ownership, or other interests */ /* in the software remain the property of Atmel Corporation. This */ /* software may only be used in accordance with the corresponding */ /* license agreement. Any un-authorized use, duplication, transmission, */ /* distribution, or disclosure of this software is expressly forbidden. */ /* */ /* This Copyright notice may not be removed or modified without prior */ /* written consent of Atmel Corporation. */ /* */ /* Atmel Corporation, Inc. reserves the right to modify this software */ /* without notice. */ /* */ /* Atmel Corporation. */ /* 2325 Orchard Parkway literature@atmel.com */ /* San Jose, CA 95131 http://www.atmel.com */ /* */ /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ /** */ /** FastVNET (PCMCIA) Linux Driver */ /** */ /***************************************************************************/ #include "vnet.h" #include #include "vnetioctl.h" #include "interrupt.h" #include /******************************************************************** * Module parameters, Globals and miscelenious definitions ********************************************************************/ static char *version = "4.1.2.31"; #ifndef PCI #if defined(R504) static dev_info_t dev_info = "fvnet4_cs"; #elif defined(REV_D) static dev_info_t dev_info = "fvnetrD_cs"; #elif defined(REV_E) static dev_info_t dev_info = "fvnetrE_cs"; #elif defined(RFMD) static dev_info_t dev_info = "fvnetr_cs"; #else static dev_info_t dev_info = "fastvnet_cs"; #endif static dev_link_t *dev_list = NULL; static u_int irq_mask = 0xdeb8; // Interrupt mask static int irq_list[4] = { -1 }; // Interrupt list (alternative) #endif //PCI static int mtu = 1500; static int eth = 1; static UCHAR channel = 4; static UCHAR TxRate = 3; static USHORT RTSThreshold = 2347; static USHORT FragThreshold = 2346; static UCHAR OpMode = INFRASTRUCTURE_MODE; static char ESSID[MAX_SSID_LENGTH+1] = ""; static UCHAR WepKeyToUse = 0; static char WepKey1[(SHORT_WEP_KEY_SIZE*2)+1]= "0000000000\0"; static char WepKey2[(SHORT_WEP_KEY_SIZE*2)+1] = "0000000000\0"; static char WepKey3[(SHORT_WEP_KEY_SIZE*2)+1] = "0000000000\0"; static char WepKey4[(SHORT_WEP_KEY_SIZE*2)+1] = "0000000000\0"; static UCHAR WepMode = WEP_MODE_MANDATORY; static UCHAR EncryptionLevel = WEP_DISABLED; static UCHAR AuthenticationType = C80211_MGMT_AAN_OPENSYSTEM; static UCHAR PreambleType = LONG_PREAMBLE; static UCHAR PwrMgmtMode = 0; static UCHAR BeaconPeriod = 100; static UCHAR RadioOn = 1; static int pc_debug = 513; MODULE_PARM(pc_debug, "i"); #ifndef PCI MODULE_PARM(irq_mask, "i"); MODULE_PARM(irq_list, "1-4i"); #endif MODULE_PARM(mtu, "i"); MODULE_PARM(eth, "i"); MODULE_PARM(channel, "i"); MODULE_PARM(TxRate, "i"); MODULE_PARM(RadioOn, "i"); MODULE_PARM(RTSThreshold, "i"); MODULE_PARM(FragThreshold, "i"); MODULE_PARM(OpMode, "i"); MODULE_PARM(ESSID, "c" __MODULE_STRING(MAX_SSID_LENGTH)); MODULE_PARM(WepKeyToUse,"i"); MODULE_PARM(WepKey1, "c" __MODULE_STRING(11)); MODULE_PARM(WepKey2, "c" __MODULE_STRING(11)); MODULE_PARM(WepKey3, "c" __MODULE_STRING(11)); MODULE_PARM(WepKey4, "c" __MODULE_STRING(11)); MODULE_PARM(WepMode, "i"); MODULE_PARM(EncryptionLevel, "i"); MODULE_PARM(AuthenticationType, "i"); MODULE_PARM(PreambleType, "i"); MODULE_PARM(PwrMgmtMode, "i"); MODULE_PARM(BeaconPeriod, "i"); // Macros not included in kernel #ifndef __IN_PCMCIA_PACKAGE__ #define DEV_KFREE_SKB(skb) dev_kfree_skb(skb); #define skb_tx_check(dev, skb) #define add_rx_bytes(stats, n) (stats)->rx_bytes += n; #define add_tx_bytes(stats, n) (stats)->tx_bytes += n; #endif // Ethernet timeout is ((400*HZ)/1000), but we should use a higher // value, because wireless transmissions are much slower #define TX_TIMEOUT ((4000*HZ)/1000) #define MAX_VNET_CARDS 16 static struct net_device *vnet_index[MAX_VNET_CARDS]; // // Local data for netdevice // struct net_local { struct net_device *dev; // backtrack device #ifndef PCI dev_link_t *link; // backtrack link dev_node_t node; #endif spinlock_t slock; // spinlock int interrupt; // interrupt struct net_device_stats stats; // device stats #ifdef WIRELESS_EXT #ifdef WIRELESS_SPY int spy_number; u_char spy_address[IW_MAX_SPY][VNet_LENGTH_OF_ADDRESS]; struct iw_quality spy_stat[IW_MAX_SPY]; #endif struct iw_statistics wstats; #endif VNet_ADAPTER Adapter; }; // Driver Info /******************************************************************** * Function Prototypes ********************************************************************/ static int vnet_tx (struct sk_buff *skb, struct net_device *dev); static int vnet_open (struct net_device *dev); static int vnet_close (struct net_device *dev); static void vnet_interrupt (int irq, void *dev_id, struct pt_regs *regs); #ifndef PCI static int vnet_config (dev_link_t *link); static void vnet_release (u_long arg); static dev_link_t *vnet_attach (void); static void vnet_detach (dev_link_t *link); static int vnet_event (event_t event, int priority, event_callback_args_t *args); static void cs_error (client_handle_t handle, int func, int ret); extern int init_module (void); extern void cleanup_module (void); #endif // PCI struct net_device_stats *vnet_get_stats (struct net_device *dev); #ifdef WIRELESS_EXT static int vnet_ioctl (struct net_device *dev, struct ifreq *rq, int cmd); struct iw_statistics *vnet_get_wireless_stats (struct net_device *dev); #endif #ifndef PCI /******************************************************************** * PCMCIA CONFIG / RELEASE ********************************************************************/ #define CS_CHECK(fn, args...) while ((last_ret=CardServices(last_fn=(fn),args))!=0) goto cs_failed #define CFG_CHECK(fn, args...) if (CardServices(fn, args) != 0) goto next_entry static int vnet_config (dev_link_t *link) { client_handle_t handle = link->handle; tuple_t tuple; cisparse_t parse; struct net_device *dev = (struct net_device *) link->priv; struct net_local *local = (struct net_local *) dev->priv; int last_fn, last_ret; u_char buf[64]; win_req_t req; memreq_t map; int rc, i, cs_r; config_info_t config; // cistpl_cftable_entry_t dflt = { 0 }; PVNet_ADAPTER Adapter = (PVNet_ADAPTER)&local->Adapter; IF_LOUD(DbgPrint("-> vnet_config(0x%p)\n", link);) // This reads the card's CONFIG tuple to find its configuration registers. tuple.DesiredTuple = CISTPL_CONFIG; tuple.Attributes = 0; tuple.TupleData = buf; tuple.TupleDataMax = sizeof(buf); tuple.TupleOffset = 0; CS_CHECK(GetFirstTuple, handle, &tuple); CS_CHECK(GetTupleData, handle, &tuple); CS_CHECK(ParseTuple, handle, &tuple, &parse); link->conf.ConfigBase = parse.config.base; link->conf.Present = parse.config.rmask[0]; // Configure card link->state |= DEV_CONFIG; // 2-05-2001 Test : Added GetConfigurationInfo. Check if needed CS_CHECK(GetConfigurationInfo, handle, &config); link->conf.Vcc = config.Vcc; // In this loop, we scan the CIS for configuration table entries, // each of which describes a valid card configuration, including // voltage, IO window, memory window, and interrupt settings. // We make no assumptions about the card to be configured: we use // just the information available in the CIS. In an ideal world, // this would work for any PCMCIA card, but it requires a complete // and accurate CIS. In practice, a driver usually "knows" most of // these things without consulting the CIS, and most client drivers // will only use the CIS to fill in implementation-defined details. tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; CS_CHECK(GetFirstTuple, handle, &tuple); while (1) { cistpl_cftable_entry_t dflt = { 0 }; cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); CFG_CHECK(GetTupleData, handle, &tuple); CFG_CHECK(ParseTuple, handle, &tuple, &parse); if (cfg->index == 0) goto next_entry; link->conf.ConfigIndex = cfg->index; // Does this card need audio output? if (cfg->flags & CISTPL_CFTABLE_AUDIO) { link->conf.Attributes |= CONF_ENABLE_SPKR; link->conf.Status = CCSR_AUDIO_ENA; } // Use power settings for Vcc and Vpp if present // Note that the CIS values need to be rescaled if (cfg->vcc.present & (1<conf.Vcc = cfg->vcc.param[CISTPL_POWER_VNOM]/10000; else if (dflt.vcc.present & (1<conf.Vcc = dflt.vcc.param[CISTPL_POWER_VNOM]/10000; if (cfg->vpp1.present & (1<conf.Vpp1 = link->conf.Vpp2 = cfg->vpp1.param[CISTPL_POWER_VNOM]/10000; else if (dflt.vpp1.present & (1<conf.Vpp1 = link->conf.Vpp2 = dflt.vpp1.param[CISTPL_POWER_VNOM]/10000; // Do we need to allocate an interrupt? if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1) link->conf.Attributes |= CONF_ENABLE_IRQ; // IO window settings link->io.NumPorts1 = link->io.NumPorts2 = 0; if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io; link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; if (!(io->flags & CISTPL_IO_8BIT)) link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; if (!(io->flags & CISTPL_IO_16BIT)) link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.BasePort1 = io->win[0].base; link->io.NumPorts1 = io->win[0].len; if (io->nwin > 1) { link->io.Attributes2 = link->io.Attributes1; link->io.BasePort2 = io->win[1].base; link->io.NumPorts2 = io->win[1].len; } } // This reserves IO space but doesn't actually enable it CFG_CHECK(RequestIO, link->handle, &link->io); // Now set up a common memory window, if needed. There is room // in the dev_link_t structure for one memory window handle, // but if the base addresses need to be saved, or if multiple // windows are needed, the info should go in the private data // structure for this device. // Note that the memory window base is a physical address, and // needs to be mapped to virtual space with ioremap() before it // is used. if ((cfg->mem.nwin > 0) || (dflt.mem.nwin > 0)) { cistpl_mem_t *mem = (cfg->mem.nwin) ? &cfg->mem : &dflt.mem; req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM; req.Base = mem->win[0].host_addr; req.Size = mem->win[0].len; req.AccessSpeed = 0; link->win = (window_handle_t)link->handle; CFG_CHECK(RequestWindow, &link->win, &req); map.Page = 0; map.CardOffset = mem->win[0].card_addr; CFG_CHECK(MapMemPage, link->win, &map); } break; next_entry: if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg; CS_CHECK(GetNextTuple, handle, &tuple); } // Allocate an interrupt line. Note that this does not assign a // handler to the interrupt, unless the 'Handler' member of the // irq structure is initialized. if (link->conf.Attributes & CONF_ENABLE_IRQ) { link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; if (irq_list[0] == -1) link->irq.IRQInfo2 = irq_mask; else for (i=0; i<4; i++) link->irq.IRQInfo2 |= 1 << irq_list[i]; link->irq.Handler = vnet_interrupt; link->irq.Instance = dev; CS_CHECK(RequestIRQ, link->handle, &link->irq); } // This actually configures the PCMCIA socket -- setting up // the I/O windows and the interrupt mapping, and putting the // card and host interface into "Memory and IO" mode. CS_CHECK(RequestConfiguration, link->handle, &link->conf); // Feed the netdevice with this info dev->irq = link->irq.AssignedIRQ; dev->base_addr = link->io.BasePort1; netif_start_queue(dev); // Report what we've done IF_LOUD(DbgPrint("%s: index 0x%02x: Vcc %d.%d", dev_info, link->conf.ConfigIndex, link->conf.Vcc/10, link->conf.Vcc%10);) if (link->conf.Vpp1) { IF_LOUD(DbgPrint(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10);) } if (link->conf.Attributes & CONF_ENABLE_IRQ) { IF_LOUD(DbgPrint(", irq %d", link->irq.AssignedIRQ);) } if (link->io.NumPorts1) { IF_LOUD(DbgPrint(", io 0x%04x-0x%04x", link->io.BasePort1, link->io.BasePort1+link->io.NumPorts1-1);) } if (link->io.NumPorts2) { IF_LOUD(DbgPrint(" & 0x%04x-0x%04x", link->io.BasePort2, link->io.BasePort2+link->io.NumPorts2-1);) } if (link->win) { IF_LOUD(DbgPrint(", mem 0x%06lx-0x%06lx", req.Base, req.Base+req.Size-1);) } IF_LOUD(DbgPrint("\n");) link->state &= ~DEV_CONFIG_PENDING; if(!eth) { for (i=0; iname, "vnet%d",i); vnet_index[i]=dev; break; } } // Register the netdevice rc = register_netdev(dev); if (rc) { IF_DEBUG_ERRORS(DbgPrint("%s: register_netdev() failed!\n", dev_info);) vnet_release((u_long)link); return 0; } IF_LOUD(DbgPrint("%s: Registered netdevice %s\n", dev_info, dev->name);) copy_dev_name(local->node, dev); local->Adapter.dev = dev; local->Adapter.IoBase = dev->base_addr; link->dev = &local->node; IF_VERY_LOUD(DbgPrint("<- vnet_config()\n");) return 1; cs_failed: cs_error(link->handle, last_fn, last_ret); vnet_release((u_long)link); IF_VERY_LOUD(DbgPrint("<- vnet_config()\n");) return 0; } static void vnet_release (u_long arg) { dev_link_t *link = (dev_link_t *) arg; struct net_device *dev = (struct net_device *) link->priv; struct net_local *local = (struct net_local *) dev->priv; PVNet_ADAPTER Adapter = &local->Adapter; IF_VERY_LOUD(DbgPrint("-> vnet_release(0x%p)\n", link);) // If the device is currently in use, we won't release // until it's actually closed. if (link->open) { IF_VERY_LOUD(DbgPrint("%s: vnet_release: release postponed, %s still locked\n", dev_info, link->dev->dev_name);) link->state |= DEV_STALE_CONFIG; return; } del_timer(Adapter->MgmtTimer); kfree(Adapter->MgmtTimer); CardStop(Adapter); Adapter->IsUp = FALSE; if (link->win) CardServices(ReleaseWindow, link->win); CardServices(ReleaseConfiguration, link->handle); if (link->io.NumPorts1) CardServices(ReleaseIO, link->handle, &link->io); if (link->irq.AssignedIRQ) CardServices(ReleaseIRQ, link->handle, &link->irq); link->state &= ~DEV_CONFIG; IF_VERY_LOUD(DbgPrint("<- vnet_release()\n");) } /******************************************************************** * PCMCIA ATTACH / DETACH ********************************************************************/ static dev_link_t *vnet_attach (void) { dev_link_t *link; struct net_device *dev; struct net_local *local; int rc; client_reg_t client_reg; IF_LOUD(DbgPrint("-> vnet_attach()\n");) // Flush stale links for (link=dev_list; link; link=link->next) if (link->state & DEV_STALE_LINK) vnet_detach(link); // Initialize the dev_link_t structure link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); memset(link, 0, sizeof(struct dev_link_t)); link->release.function = &vnet_release; link->release.data = (u_long) link; link->conf.Vcc = 33; link->conf.IntType = INT_MEMORY_AND_IO; // Allocate space for netdevice (private data of link) dev = kmalloc(sizeof(struct net_device), GFP_KERNEL); memset(dev, 0, sizeof(struct net_device)); link->priv = dev; // Allocate space for netdevice priv (private data of netdevice) local = kmalloc(sizeof(struct net_local), GFP_KERNEL); memset(local, 0, sizeof(struct net_local)); dev->priv = local; // Initialize specific data local->link = link; local->dev = dev; spin_lock_init(&local->slock); /* Set the mgmt timer */ local->Adapter.MgmtTimer=kmalloc(sizeof(struct timer_list), GFP_KERNEL); init_timer(local->Adapter.MgmtTimer); local->Adapter.MgmtTimer->function = MgmtTimer; local->Adapter.MgmtTimer->data = (unsigned long) dev; // Standard setup for generic data ether_setup(dev); // kernel callbacks dev->open = vnet_open; dev->stop = vnet_close; dev->hard_start_xmit = vnet_tx; dev->get_stats = vnet_get_stats; #ifdef WIRELESS_EXT dev->do_ioctl = vnet_ioctl; dev->get_wireless_stats = vnet_get_wireless_stats; #endif // Other netdevice data init_dev_name(dev, local->node); dev->mtu = mtu; netif_stop_queue(dev); // Register with CardServices link->next = dev_list; dev_list = link; client_reg.dev_info = &dev_info; client_reg.Attributes = INFO_IO_CLIENT; client_reg.EventMask = CS_EVENT_REGISTRATION_COMPLETE | CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; client_reg.event_handler = &vnet_event; client_reg.Version = 0x0210; client_reg.event_callback_args.client_data = link; rc = CardServices(RegisterClient, &link->handle, &client_reg); if (rc) { cs_error(link->handle, RegisterClient, rc); vnet_detach(link); return NULL; } IF_VERY_LOUD(DbgPrint("<- vnet_attach()\n");) return link; } static void vnet_detach (dev_link_t *link) { dev_link_t **linkp; IF_VERY_LOUD(DbgPrint("-> vnet_detach(0x%p)\n", link);) // Locate device structure for (linkp=&dev_list; *linkp; linkp=&(*linkp)->next) if (*linkp == link) break; if (!*linkp) { IF_DEBUG_ERRORS(DbgPrint("%s: Attempt to detach non-existing PCMCIA client!\n", dev_info);) return; } // If the device is currently configured and active, we won't // actually delete it yet. Instead, it is marked so that when the // release() function is called, that will trigger a proper // detach() del_timer(&link->release); if (link->state & DEV_CONFIG) { IF_LOUD(DbgPrint("%s: vnet_detach: detach postponed, %s still locked\n", dev_info, link->dev->dev_name);) vnet_release((u_long)link); if (link->state & DEV_STALE_CONFIG) { link->state |= DEV_STALE_LINK; return; } } // Break the line with CardServices if (link->handle) CardServices(DeregisterClient, link->handle); // Unlink device structure, free pieces *linkp = link->next; if (link->priv) { struct net_device *dev = (struct net_device *) link->priv; if (link->dev) { unregister_netdev(dev); IF_LOUD(DbgPrint("%s: Netdevice unregistered\n", dev_info);) } if (dev->priv) kfree(dev->priv); kfree(link->priv); } kfree(link); IF_VERY_LOUD(DbgPrint("<- vnet_detach()\n");) } /******************************************************************** * PCMCIA EVENT HANDLER ********************************************************************/ static int vnet_event (event_t event, int priority, event_callback_args_t *args) { dev_link_t *link = (dev_link_t *) args->client_data; struct net_device *dev = (struct net_device *) link->priv; struct net_local *local = (struct net_local *) dev->priv; PVNet_ADAPTER Adapter = &local->Adapter; IF_LOUD(DbgPrint("-> vnet_event(%s, %d, 0x%p)\n", ((event==CS_EVENT_REGISTRATION_COMPLETE) ? "registration complete" : ((event==CS_EVENT_CARD_INSERTION) ? "card insertion" : ((event==CS_EVENT_CARD_REMOVAL) ? "card removal" : ((event==CS_EVENT_RESET_PHYSICAL) ? "physical physical" : ((event==CS_EVENT_CARD_RESET) ? "card reset" : ((event==CS_EVENT_PM_SUSPEND) ? "pm suspend" : ((event==CS_EVENT_PM_RESUME) ? "pm resume" : "unknown"))))))), priority, args);) switch (event) { case CS_EVENT_CARD_INSERTION: link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; if (!vnet_config(link)) dev->irq = 0; Adapter->IsUp = TRUE; InitAdapter(&local->Adapter); SetParameters(&local->Adapter); if (!InitAndStartCard(&local->Adapter)) return -1; memcpy(dev->dev_addr, Adapter->CurrentAddress, 6); break; case CS_EVENT_CARD_REMOVAL: del_timer(Adapter->MgmtTimer); link->state &= ~DEV_PRESENT; Adapter->IsUp = FALSE; if (link->state & DEV_CONFIG) { netif_stop_queue(dev); netif_device_detach(dev); mod_timer(&link->release, jiffies + HZ/20); } break; case CS_EVENT_PM_SUSPEND: link->state |= DEV_SUSPEND; case CS_EVENT_RESET_PHYSICAL: Adapter->IsUp = FALSE; if (link->state & DEV_CONFIG) { if (link->open) { netif_stop_queue(dev); netif_device_detach(dev); } CardServices(ReleaseConfiguration, link->handle); } break; case CS_EVENT_PM_RESUME: link->state &= ~DEV_SUSPEND; // Fall through case CS_EVENT_CARD_RESET: if (link->state & DEV_CONFIG) { CardServices(RequestConfiguration, link->handle, &link->conf); if (link->open) { netif_device_attach(dev); netif_start_queue(dev); } } break; } IF_VERY_LOUD(DbgPrint("<- vnet_event()\n");) return 0; } /******************************************************************** * MODULE INSERTION / REMOVAL ********************************************************************/ extern int init_module (void) { servinfo_t serv; IF_VERY_LOUD(DbgPrint("-> init_module()\n");) IF_LOUD(DbgPrint("%s: ATMEL 11Mbps Wireless PCMCIA LAN driver version %s\n", dev_info, version);) // Check CardServices release CardServices(GetCardServicesInfo, &serv); if (serv.Revision != CS_RELEASE_CODE){ printk("%s: CardServices release does not match!\n", dev_info); return -1; } // Register PCMCIA driver register_pccard_driver(&dev_info, &vnet_attach, &vnet_detach); IF_VERY_LOUD(DbgPrint("<- init_module()\n");) return 0; } extern void cleanup_module (void) { IF_VERY_LOUD(DbgPrint("-> cleanup_module()\n");) // Unregister PCMCIA driver unregister_pccard_driver(&dev_info); // Remove leftover devices if (dev_list) { IF_VERY_LOUD(DbgPrint("%s: Removing leftover devices!\n", dev_info);) } while (dev_list) { if (dev_list->state & DEV_CONFIG) vnet_release((u_long)dev_list); vnet_detach(dev_list); } IF_LOUD(DbgPrint("%s: Driver unloaded\n", dev_info);) IF_VERY_LOUD(DbgPrint("<- cleanup_module()\n");) } // Show CardServices error message (syslog) static void cs_error (client_handle_t handle, int func, int ret) { error_info_t err = { func, ret }; CardServices(ReportError, handle, &err); } #endif //ifndef PCI /******************************************************************** * NET OPEN / CLOSE ********************************************************************/ static int vnet_open (struct net_device *dev) { struct net_local *local = (struct net_local *) dev->priv; #ifndef PCI struct dev_link_t *link = local->link; #endif IF_LOUD(DbgPrint("-> vnet_open(%s)\n", dev->name);) netif_device_attach(dev); netif_start_queue(dev); local->interrupt = 0; #ifndef PCI link->open++; #endif MOD_INC_USE_COUNT; IF_VERY_LOUD(DbgPrint("<- vnet_open(%s)\n", dev->name);) return 0; } static int vnet_close (struct net_device *dev) { struct net_local *local = (struct net_local *) dev->priv; #ifndef PCI struct dev_link_t *link = local->link; // If the device isn't open, then nothing to do if (!link->open) { IF_VERY_LOUD(DbgPrint("<> vnet_close(%s)\n", dev->name);) return 0; } IF_VERY_LOUD(DbgPrint("-> vnet_close(%s)\n", dev->name);) // Close the device link->open--; #endif MOD_DEC_USE_COUNT; // Check if card is still present if (netif_running(dev)) { netif_stop_queue(dev); netif_device_detach(dev); } #ifndef PCI else if (link->state & DEV_STALE_CONFIG) mod_timer(&link->release, jiffies + HZ/20); #endif IF_VERY_LOUD(DbgPrint("<- vnet_close(%s)\n", dev->name);) return -EINVAL; } /******************************************************************** * NET Statistics ********************************************************************/ struct net_device_stats *vnet_get_stats (struct net_device *dev) { struct net_local *local = (struct net_local *)dev->priv; PVNet_ADAPTER Adapter = &local->Adapter; IF_VERY_LOUD(DbgPrint("<> vnet_get_stats(%s)\n", dev->name);) local->stats.tx_packets=Adapter->Stats.TxDataPacketsOk; local->stats.rx_packets=Adapter->Stats.RxDataPacketsOk; local->stats.tx_errors=Adapter->Stats.TxDataPacketsError; local->stats.rx_errors=Adapter->Stats.RxDataPacketsError; return(&((struct net_local *) dev->priv)->stats); } /******************************************************************* Management Timer *******************************************************************/ VOID MgmtTimer(u_long a) { struct net_device *dev = (struct net_device *) a; struct net_local *local = (struct net_local *)dev->priv; PVNet_ADAPTER Adapter= &local->Adapter; if(!Adapter->IsUp #ifndef PCI || !(local->link->state & DEV_PRESENT) #endif ) { IF_LOUD(DbgPrint("** MgmtTimer reject ***\n");) return; } MgmtTimeOutCallBack(Adapter); } /******************************************************************** * NET TX / RX ********************************************************************/ int vnet_tx (struct sk_buff *skb, struct net_device *dev) { struct net_local *local = (struct net_local *)dev->priv; ULONG TotalBytes=0; USHORT StartOfTxBuffer=0; unsigned long flags; ULONG Status; int len; UCHAR *p; PVNet_ADAPTER Adapter = &local->Adapter; IF_VERY_LOUD(DbgPrint("-> vnet_tx(%s)\n", dev->name);) if(Adapter->StationState != STATION_STATE_READY #ifndef PCI || !(local->link->state & DEV_PRESENT) #endif ) return 0; #if (KERNEL_VERSION_CODE < KERNEL_VERSION(2,3,42)) if (netif_queue_stopped(dev)) { #ifndef PCI IF_VERY_LOUD(DbgPrint("%s: vnet_tx(%s) called while busy!\n", dev_info, dev->name);) #endif if ((jiffies - dev->trans_start) < TX_TIMEOUT) return 1; if (!netif_running(dev)) { #ifndef PCI IF_LOUD(DbgPrint("%s: %s Tx on stopped device!\n", dev_info, dev->name);) #endif return 1; } } #endif skb_tx_check(dev, skb); #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,1,79)) skb->arp = 1; #endif // Disable interrupts spin_lock_irqsave(&local->slock, flags); // Prepare packet p = skb->data; len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN; TotalBytes = len; if ( TxResourcesAvailable(Adapter, TotalBytes+18, &StartOfTxBuffer) == FALSE) { Adapter->Stats.RxLost++; netif_stop_queue(dev); spin_unlock_irqrestore(&local->slock, flags); return 1; } TxEthernetPacket(Adapter,skb->data, len, &TotalBytes, StartOfTxBuffer, TRUE); TxUpdateDescriptor(Adapter, TotalBytes, StartOfTxBuffer, TRUE); // Send packet // Remember time transmission and count tx bytes dev->trans_start = jiffies; add_tx_bytes(&local->stats, len); // Re-enable interrupts spin_unlock_irqrestore(&local->slock, flags); DEV_KFREE_SKB(skb); IF_VERY_LOUD(DbgPrint("<- vnet_tx()\n");) return 0; } /******************************************************************** * INTERRUPT HANDLER */ static void vnet_interrupt (int irq, void *dev_id, struct pt_regs *regs) { UCHAR InterruptStatus; INTERRUPT_TYPE InterruptType; #ifdef PCI struct net_device *dev = ((struct pci_dev *) dev_id)->driver_data; #else struct net_device *dev = (struct net_device *) dev_id; #endif struct net_local *local = (struct net_local *) dev->priv; PVNet_ADAPTER Adapter = &local->Adapter; int cnt; IF_VERY_LOUD(DbgPrint("-> vnet_interrupt(%d)\n", irq);) if(!Adapter->IsUp #ifndef PCI || !(local->link->state & DEV_PRESENT) #endif ) return; // Check device if (!dev) { #ifndef PCI IF_LOUD(DbgPrint("%s: IRQ %d for unknown device!\n", dev_info, irq);) #endif return; } DisableInterrupts(Adapter); if (test_and_set_bit(0, (void *)&local->interrupt)){ #ifdef PCI IF_LOUD(DbgPrint("Warning: IRQ %d Reentering interrupt handler!\n", irq);) #else IF_LOUD(DbgPrint("%s: Warning: IRQ %d Reentering interrupt handler!\n", dev_info, irq);) #endif } ProcessInterrupt(Adapter); local->interrupt = 0; // Turn back interrupts on (unlock) EnableInterrupts(Adapter); IF_VERY_LOUD(DbgPrint("<- vnet_interrupt()\n");) } VOID UpdateSupportAppInfo(PVNet_ADAPTER Adapter, PDEVICE_CONFIGURATION pDevConfig) { DEVICE_CONFIGURATION DevConfig; pDevConfig->StationState = Adapter->StationState; pDevConfig->OperatingMode = Adapter->OperatingMode; pDevConfig->Channel = (Adapter->Channel & 0x7f); VNetMoveMemory(pDevConfig->SSID, Adapter->DesiredSSID, Adapter->SSID_Size); pDevConfig->SSIDlength = Adapter->SSID_Size; VNetMoveMemory(pDevConfig->BSSID, Adapter->CurrentBSSID, 6); pDevConfig->PreambleType = Adapter->PreambleType; pDevConfig->PowerMgmtMode = Adapter->PowerMgmtMode; pDevConfig->TxRate = Adapter->DataFramesTxRate; pDevConfig->FragmentationThreshold = Adapter->FragmentationThreshold; pDevConfig->RtsCtsThreshold = Adapter->RtsThreshold; pDevConfig->Rssi = Adapter->Rssi; pDevConfig->LinkQuality = 40 - Adapter->LinkQuality; } // IO CONTROLS #ifdef WIRELESS_EXT #define SuP 1 #define WeP 2 static int vnet_ioctl (struct net_device *dev, struct ifreq *rq, int cmd) { struct net_local *local=(struct net_local *) dev->priv; struct iwreq *wrq = (struct iwreq *) rq; USHORT subcmd; PVNet_ADAPTER Adapter = &local->Adapter; ULONG GenericULong; UCHAR ApInfoList[MAX_BSS_ENTRIES*sizeof(BSS_INFO)]; char buff[256]; int rc=0, update=0, WEP_OR_SUPPORT=0, APindex; DEVICE_CONFIGURATION DevConfig; // check the user. if not superuser return no perm. if (IW_IS_SET(cmd) && cmd >= SIOCGIWNAME && !suser()) return -EPERM; DisableInterrupts(Adapter); UpdateSupportAppInfo(Adapter, &DevConfig); IF_LOUD(DbgPrint("Calling ioctl\n");) switch(cmd) { case SIOCGIWNAME: #if defined(R504) strcpy(wrq->u.name, "ATMEL 504"); #elif defined(PCI) strcpy(wrq->u.name, "ATMEL PCI"); #elif defined(REV_D) strcpy(wrq->u.name, "ATMEL REVD"); #elif defined(REV_E) strcpy(wrq->u.name, "ATMEL REVE"); #elif defined(RFMD) strcpy(wrq->u.name, "ATMEL RFMD"); #else strcpy(wrq->u.name, "ATMEL INTERSIL"); #endif break; case SIOCDEVPRIVATE: subcmd=wrq->u.data.flags; switch(subcmd) { case GET_SUPPORT_INFO: wrq->u.data.length = sizeof(DEVICE_CONFIGURATION); copy_to_user(wrq->u.data.pointer, &DevConfig, wrq->u.data.length); break; case SET_SUPPORT_INFO: copy_from_user(&DevConfig, wrq->u.data.pointer, wrq->u.data.length); ChangeConfiguration(Adapter, &DevConfig); break; case GET_WEP_INFO: wrq->u.data.length = sizeof(WEP_INFO); copy_to_user(wrq->u.data.pointer, &Adapter->WepInfo, wrq->u.data.length); break; case SET_WEP_INFO: copy_from_user(&Adapter->WepInfo,wrq->u.data.pointer,wrq->u.data.length); ConfigureWEP(Adapter); ResetAdapter(Adapter); break; case GET_VERSION_INFO: Adapter->VersionInfo.DriverMajorVersion = FAST_VNET_MAJOR_VERSION; Adapter->VersionInfo.DriverMinorVersion = FAST_VNET_MINOR_VERSION; Adapter->VersionInfo.DriverSubVersion = FAST_VNET_SUB_VERSION; Adapter->VersionInfo.DriverBuild = FAST_VNET_BUILD; Adapter->VersionInfo.FwMajorVersion = Adapter->HostInfo.MajorVer; Adapter->VersionInfo.FwMinorVersion = Adapter->HostInfo.MinorVer; Adapter->VersionInfo.FwSubVersion = 0; Adapter->VersionInfo.FwBuild = Adapter->HostInfo.BuildVer; wrq->u.data.length = sizeof(VERSION_INFO); copy_to_user(wrq->u.data.pointer, &Adapter->VersionInfo, wrq->u.data.length); break; case RESET_CARD: ResetAdapter(Adapter);break; case CLEAR_PACKETS: copy_from_user(buff, wrq->u.data.pointer, wrq->u.data.length); VNetZeroMemory((PUCHAR)&Adapter->Stats, sizeof(STATISTICS)); update = 0; break; case GET_AP_INFO: wrq->u.data.length = sizeof(BSS_INFO)*Adapter->BSSListEntries; copy_to_user(wrq->u.data.pointer, &Adapter->BSSinfo, wrq->u.data.length); break; case SITE_SURVEY: if(Adapter->SiteSurveyState == SITE_SURVEY_IDLE || Adapter->SiteSurveyState == SITE_SURVEY_COMPLETED) { Adapter->SiteSurveyState = SITE_SURVEY_IN_PROGRESS; Scan(Adapter, FALSE); } GenericULong = 0; wrq->u.data.length = 0; break; case STOP_SITE_SURVEY: Adapter->SiteSurveyState = SITE_SURVEY_IDLE; SelectBSS(Adapter, 0xff); //ff indicates no AP selected break; case SELECT_AP_BY_INDEX: copy_from_user(&APindex, wrq->u.data.pointer, wrq->u.data.length); SelectBSS(Adapter, APindex-1); Adapter->SiteSurveyState = SITE_SURVEY_IDLE; break; case SITE_SURVEY_STATE: wrq->u.data.length = sizeof(UCHAR); copy_to_user(wrq->u.data.pointer, &Adapter->SiteSurveyState, wrq->u.data.length); break; case GET_STATISTICS: wrq->u.data.length = sizeof(STATISTICS); copy_to_user(wrq->u.data.pointer, &Adapter->Stats, wrq->u.data.length); break; case GET_MAC_ADDRESS: wrq->u.data.length = VNet_LENGTH_OF_ADDRESS; copy_to_user(wrq->u.data.pointer, &Adapter->CurrentAddress, wrq->u.data.length); update=0; break; } break; // GETS case SIOCGIWESSID: // Return Current ESSID to wireless tools wrq->u.data.flags=1; wrq->u.data.length=Adapter->SSID_Size; copy_to_user(wrq->u.data.pointer, Adapter->DesiredSSID, wrq->u.data.length); break; case SIOCGIWMODE: switch(Adapter->OperatingMode) { case AD_HOC_MODE: wrq->u.mode=Adapter->OperatingMode;break; case INFRASTRUCTURE_MODE: wrq->u.mode=0;break; // For compatibility with wireless tools // AdHoc in wt is 1 and for infrastructure return 0 (Auto in wt) } break; case SIOCGIWNWID: //return -1. This is if ioctl failed and as a result return -1; //wt(wireless tools) won't print a network ID break; case SIOCGIWNICKN: //same as above, for the nickname return -1; break; case SIOCGIWFRAG: //fragmentation threshold wrq->u.frag.value=Adapter->FragmentationThreshold; wrq->u.frag.fixed=1; break; case SIOCGIWRTS: //RTS threshold wrq->u.rts.value=Adapter->RtsThreshold; wrq->u.rts.fixed=1; break; case SIOCGIWFREQ: //Return m,e wrq->u.freq.m = Adapter->Channel; wrq->u.freq.e = 0; break; case SIOCGIWAP: //Access Point Address VNetMoveMemory(wrq->u.ap_addr.sa_data, Adapter->CurrentBSSID, 6); wrq->u.ap_addr.sa_family = ARPHRD_ETHER; break; case SIOCGIWPOWER: //Power Management return -1; break; case SIOCGIWENCODE: wrq->u.data.length=sizeof(WEP_INFO); wrq->u.data.flags=IW_ENCODE_DISABLED; //Wrong...for the moment copy_to_user(wrq->u.data.pointer, &Adapter->WepInfo, wrq->u.data.length); break; case SIOCGIWRATE: //Bitrate Used wrq->u.bitrate.fixed=0; switch(Adapter->DataFramesTxRate) { case 0: wrq->u.bitrate.value=1e6; break; case 1: wrq->u.bitrate.value=2e6; break; case 2: wrq->u.bitrate.value=5.5e6; break; case 3: wrq->u.bitrate.value=11e6; break; default: wrq->u.bitrate.value=2e9; break; } break; case SIOCGIWSENS://AP Density. Currently not supported return -1; break; // Sets case SIOCSIWESSID://ESSID change VNetMoveMemory(DevConfig.SSID, wrq->u.data.pointer, wrq->u.data.length); DevConfig.SSIDlength = wrq->u.data.length; IF_VERY_LOUD(DbgPrint("New ESSID %s.\n",DevConfig.SSID);) update=1; WEP_OR_SUPPORT=SuP; break; case SIOCSIWRTS: DevConfig.RtsCtsThreshold=wrq->u.rts.value; update=1; WEP_OR_SUPPORT=SuP; break; case SIOCSIWFRAG: DevConfig.FragmentationThreshold=wrq->u.frag.value; update=1; WEP_OR_SUPPORT=SuP; break; case SIOCSIWMODE: if ( (wrq->u.mode<0)||(wrq->u.mode>1) ) return -3; DevConfig.OperatingMode=2-wrq->u.mode; update=1; WEP_OR_SUPPORT=SuP; break; case SIOCGIWRANGE: return -1; break; } // After Getting Request Check if Updates are Needed and if so // Run Configure_WEP(1) or ChangeConfiguration(2) if (update==1) { switch(WEP_OR_SUPPORT) { case WeP: ConfigureWEP(Adapter); ResetAdapter(Adapter); break; case SuP: ChangeConfiguration(Adapter, &DevConfig); //Reset ? break; default: //Case of error return -1; break; } update=0; } EnableInterrupts(Adapter); return rc; } // Iwconfig statistics... struct iw_statistics *vnet_get_wireless_stats (struct net_device *dev) { struct net_local *local = (struct net_local *) dev->priv; PVNet_ADAPTER Adapter = &local->Adapter; local->wstats.status=0; IF_VERY_LOUD(DbgPrint("-> vnet_get_wireless_stats(%s)\n", dev->name);) if (Adapter->OperatingMode == INFRASTRUCTURE_MODE) { local->wstats.qual.qual = 40 - Adapter->LinkQuality; local->wstats.qual.level = Adapter->Rssi; local->wstats.qual.noise = 0;//max(min(ltv.noise_lvl, 0x8a), 0x2f) - 0x95; local->wstats.qual.updated = 7; } else { // Quality levels cannot be determined in ad-hoc mode, // because we can 'hear' more that one remote station. local->wstats.qual.qual = 0; local->wstats.qual.level = 0; local->wstats.qual.noise = 0; local->wstats.qual.updated = 0; } // Packets discarded in the wireless adapter due to wireless specific problems local->wstats.discard.nwid = 0; local->wstats.discard.code = 0; local->wstats.discard.misc = 0; IF_VERY_LOUD(DbgPrint("<- vnet_get_wireless_stats()\n");) return (&local->wstats); } #endif // Kernel Specific Functions VOID SetMgmtTimer(PVNet_ADAPTER Adapter) { #if (LINUX_VERSION_CODE < VERSION(2,3,0)) if(Adapter->MgmtTimer->prev == (struct timer_list *) NULL) { /* set timer to expire in WATCHDOG_JIFFIES */ Adapter->MgmtTimer->expires = jiffies + MGMT_JIFFIES; add_timer(Adapter->MgmtTimer); } #else if(Adapter->MgmtTimer->list.prev == (struct list_head *) NULL) { /* set timer to expire in WATCHDOG_JIFFIES */ Adapter->MgmtTimer->expires = jiffies + MGMT_JIFFIES; add_timer(Adapter->MgmtTimer); } #endif } VOID VnetSleep(ULONG stime) { mdelay(stime/1000); } VOID VNet_netif_wake_queue(PVNet_ADAPTER Adapter) { netif_wake_queue(Adapter->dev); } VOID RxIndicatePacket(PVNet_ADAPTER Adapter, PUCHAR pRxBuf, ULONG PacketLength, ULONG IndicateLen) { struct net_device *dev = (struct net_device *) Adapter->dev; struct sk_buff *skb; skb = dev_alloc_skb(PacketLength+2); if(!skb) { Adapter->Stats.RxLost++; return; } skb_reserve(skb, 2); VNetMoveMemory(skb_put(skb, PacketLength), pRxBuf, PacketLength); Adapter->dev->last_rx=jiffies; skb->dev = dev; skb->protocol = eth_type_trans(skb, dev); skb->ip_summed = CHECKSUM_NONE; netif_rx(skb); return; } #if defined(REV_D) #include "fwr_d.h" #elif defined(REV_E) #include "fwr_e.h" #elif defined(R504) #include "fwr504.h" #elif defined(PCI) #include "fwr506.h" #elif defined(RFMD) #include "fwr.h" #else #include "fw.h" #endif //REV_D BOOLEAN GetFwFromFile(PVNet_ADAPTER Adapter) { return FALSE; } BOOLEAN GetDefaultFw(PVNet_ADAPTER Adapter) { ULONG SoFw = sizeof(BasicFW); IF_LOUD(DbgPrint("--=+>GetFwFile BasicFW : %d, \n",SoFw);) Adapter->MappedFirmwareBuffer = (PUCHAR)kmalloc(SoFw, GFP_KERNEL); IF_LOUD(DbgPrint("Fw Mapped in : 0x%08X\n",Adapter->MappedFirmwareBuffer);) VNetZeroMemory(Adapter->MappedFirmwareBuffer, SoFw); VNetMoveMemory(Adapter->MappedFirmwareBuffer, BasicFW, SoFw); Adapter->FirmwareFileLength = SoFw; IF_LOUD(DbgPrint("<=- Out of GetFwFile\n");) return TRUE; } #ifdef PCI int vnet_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { long ioaddr0, ioaddr1; unsigned short i, tmp; int res, ret; struct net_local *local; unsigned short basemem = 0; local = kmalloc(sizeof(struct net_local), GFP_KERNEL); if(!local) return -ENOMEM; memset(local, 0, sizeof(struct net_local)); res = pci_enable_device(dev); if(res<0) { DbgPrint(__FUNCTION__" Failed to enable device %d\n",res); return res; } // ioaddr0 = pci_resource_start(dev, 0); ioaddr1 = pci_resource_start(dev, 1); if(!ioaddr1 || (!(pci_resource_flags(dev, 1) & IORESOURCE_IO)) ) return -ENODEV; if(request_region(ioaddr1, VNET_IO_LENGTH, "pcifvnet")==NULL) return -EBUSY; ret = request_irq(dev->irq, vnet_interrupt, SA_SHIRQ, dev->name, dev); if(ret!=0) return ret; DbgPrint(__FILE__" "__FUNCTION__" Io0 %08x, Io1 %04x, Irq %d\n",ioaddr0, ioaddr1, dev->irq); local->Adapter.pci = dev; local->Adapter.IoBase = ioaddr1; local->Adapter.dev = init_etherdev(NULL, 0); if(local->Adapter.dev == NULL) return -1; local->Adapter.pci = dev; IF_LOUD(DbgPrint(__FILE__" "__FUNCTION__"Net Device : 0x%08x\n ", local->Adapter.dev);) local->Adapter.IsUp = TRUE; spin_lock_init(&local->slock); InitAdapter(&local->Adapter); local->Adapter.pci->driver_data = local->Adapter.dev; local->Adapter.dev->open = vnet_open; local->Adapter.dev->stop = vnet_close; local->Adapter.dev->hard_start_xmit = vnet_tx; local->Adapter.dev->priv = local; local->Adapter.dev->addr_len = 6; local->Adapter.dev->mtu = mtu; local->Adapter.dev->irq = dev->irq; local->Adapter.dev->base_addr = ioaddr1; memset(local->Adapter.dev->dev_addr, 0xff, 6); IF_LOUD(DbgPrint("To Set Parameters -->\n");) SetParameters(&local->Adapter); IF_LOUD(DbgPrint("Init And Start Card\n");) if(!InitAndStartCard(&local->Adapter)) return -1; memcpy(local->Adapter.dev->dev_addr, local->Adapter.CurrentAddress, 6); register_netdev(local->Adapter.dev); DbgPrint(__FUNCTION__" Mac : %02x:%02x:%02x:%02x:%02x:%02x\n", local->Adapter.dev->dev_addr[0],local->Adapter.dev->dev_addr[1],local->Adapter.dev->dev_addr[2], local->Adapter.dev->dev_addr[3],local->Adapter.dev->dev_addr[4],local->Adapter.dev->dev_addr[5]); // Initialize specific data local->Adapter.MgmtTimer=kmalloc(sizeof(struct timer_list), GFP_KERNEL); init_timer(local->Adapter.MgmtTimer); local->Adapter.MgmtTimer->function = MgmtTimer; local->Adapter.MgmtTimer->data = (unsigned long) local->Adapter.dev; local->Adapter.dev->get_stats = vnet_get_stats; #ifdef WIRELESS_EXT local->Adapter.dev->do_ioctl = vnet_ioctl; local->Adapter.dev->get_wireless_stats = vnet_get_wireless_stats; #endif EnableInterrupts(&local->Adapter); printk("ATMEL Wireless PCI card version %d.%d.%d.%d\n", FAST_VNET_MAJOR_VERSION, FAST_VNET_MINOR_VERSION, FAST_VNET_SUB_VERSION, FAST_VNET_BUILD); return 0; } void vnet_pci_remove(struct pci_dev *dev) { struct net_device *net = (struct net_device *)dev->driver_data; PVNet_ADAPTER Adapter = (PVNet_ADAPTER)(&((struct net_local *)net->priv)->Adapter); DbgPrint("Device Disconnecting\n"); DisableInterrupts(Adapter); del_timer(Adapter->MgmtTimer); netif_stop_queue(net); netif_device_detach(net); Adapter->IsUp = FALSE; free_irq(dev->irq, dev); unregister_netdev(net); net->priv = NULL; net->name[0] = '\0'; kfree(net); release_region(Adapter->IoBase, VNET_IO_LENGTH); } struct pci_device_id vnet_pci_id_table = { vendor : 0x1114, device : 0x0506 }; struct pci_driver vnet_pci_driver = { name : "pcifvnet", id_table: &vnet_pci_id_table, probe : &vnet_pci_probe, remove : &vnet_pci_remove }; int __init vnet_init(void) { DbgPrint(KERN_INFO"registering\n"); return pci_module_init( &vnet_pci_driver ); } void __exit vnet_exit(void) { pci_unregister_driver( &vnet_pci_driver ); } MODULE_AUTHOR("DriversGroup"); module_init(vnet_init); module_exit(vnet_exit); #endif