A driver needs its private structure to do card initialization. The private structure is allocated together with net_device; so we need to allocate net_device first, then allow driver to do its setup and then register net_device to the kernel. Current function ieee80211_register_hw() does not allow this. This patch moves allocation from ieee80211_register_hw() to ieee80211_alloc_hw() and deallocation from ieee80211_unregister_hw() to ieee8021_free_hw(). These changes shifted the meaning of ieee80211_hw_initialized(), so it is renamed to ieee80211_update_hw(). A small memory leak is fixed too. Signed-off-by: Jiri Benc Index: netdev/include/net/ieee80211.h =================================================================== --- netdev.orig/include/net/ieee80211.h 2005-12-21 19:48:06.000000000 +0100 +++ netdev/include/net/ieee80211.h 2005-12-21 19:51:45.000000000 +0100 @@ -596,31 +596,35 @@ struct ieee80211_hw { int (*atheros_xr_in_use)(struct net_device *dev, int in_use); }; -/* Register hardware device to the IEEE 802.11 code and kernel. Low-level - * drivers must call this function before using any other IEEE 802.11 - * function. This must be called once for each hardware device. The returned - * pointer must be used to refer to this device when calling other functions. - * 802.11 code allocates a private data area for the low-level driver. The - * size of this area is given as priv_data_len. ieee80211_dev_hw_data() is - * used to get a pointer to the private data area. +/* Allocate a new hardware device. This must be called once for each + * hardware device. The returned pointer must be used to refer to this + * device when calling other functions. 802.11 code allocates a private data + * area for the low-level driver. The size of this area is given as + * priv_data_len. ieee80211_dev_hw_data() is used to get a pointer to the + * private data area. * * Note: in this version of the interface the returned pointer is struct * net_device *. This may change in the future and low-level driver should * not refer the device data directly to remain compatible with the future * versions of the interface. */ -struct net_device * ieee80211_register_hw(struct ieee80211_hw *hw, - size_t priv_data_len); +struct net_device *ieee80211_alloc_hw(size_t priv_data_len, + void (*setup)(struct net_device *)); + +/* Register hardware device to the IEEE 802.11 code and kernel. Low-level + * drivers must call this function before using any other IEEE 802.11 + * function. */ +int ieee80211_register_hw(struct net_device *dev, struct ieee80211_hw *hw); -/* Inform IEEE 802.11 code that the low-level driver has finished hardware - * initialization. This function is allowed to update hardware configuration - * (e.g., list of supported operation modes and rates). */ -void ieee80211_hw_initialized(struct net_device *dev, struct ieee80211_hw *hw); +/* This function is allowed to update hardware configuration (e.g., list of + * supported operation modes and rates). */ +int ieee80211_update_hw(struct net_device *dev, struct ieee80211_hw *hw); /* Unregister a hardware device. This function instructs 802.11 code to free - * allocated resources and unregister netdevices from the kernel. Non-zero - * return value indicates that unregistration was not allowed and the - * low-level must continue to accept calls to registered handler functions. */ -int ieee80211_unregister_hw(struct net_device *dev); + * allocated resources and unregister netdevices from the kernel. */ +void ieee80211_unregister_hw(struct net_device *dev); + +/* Free allocated net_device including private data of a driver. */ +void ieee80211_free_hw(struct net_device *dev); /* Receive frame callback function. The low-level driver uses this function to * send received frames to the IEEE 802.11 code. Receive buffer (skb) must Index: netdev/net/ieee80211/ieee80211.c =================================================================== --- netdev.orig/net/ieee80211/ieee80211.c 2005-12-21 19:48:06.000000000 +0100 +++ netdev/net/ieee80211/ieee80211.c 2005-12-21 19:51:54.000000000 +0100 @@ -4370,30 +4370,14 @@ static void ieee80211_precalc_rates(stru } -struct net_device * ieee80211_register_hw(struct ieee80211_hw *hw, - size_t priv_data_len) +struct net_device *ieee80211_alloc_hw(size_t priv_data_len, + void (*setup)(struct net_device *)) { struct net_device *dev, *apdev, *mdev; struct ieee80211_local *local; struct ieee80211_sub_if_data *sdata; int alloc_size; - if (!hw || !hw->modes || !hw->modes->channels || !hw->modes->rates || - !hw->modes->num_channels || !hw->modes->num_rates) - return NULL; - - if (hw->version != IEEE80211_VERSION) { - printk("ieee80211_register_hw - version mismatch: 80211.o " - "version %d, low-level driver version %d\n", - IEEE80211_VERSION, hw->version); - return NULL; - } - - /* Backwards compatibility for low-level drivers that do not set number - * of TX queues. */ - if (hw->queues == 0) - hw->queues = 1; - /* Ensure 32-bit alignment of our private data and hw private data. * Each net_device is followed by a sub_if_data which which is used * for wds/vlan information; it is aligned as well. @@ -4427,11 +4411,10 @@ struct net_device * ieee80211_register_h sizeof(struct net_device) + 3 + sizeof(struct ieee80211_sub_if_data) + 3 + 4096; - mdev = (struct net_device *) kmalloc(alloc_size, GFP_KERNEL); + mdev = (struct net_device *) kzalloc(alloc_size, GFP_KERNEL); if (mdev == NULL) return NULL; - memset(mdev, 0, alloc_size); mdev->priv = (struct net_device *) (((long) mdev + sizeof(struct net_device) + @@ -4462,24 +4445,13 @@ struct net_device * ieee80211_register_h dev->tx_queue_len = 0; dev->set_mac_address = ieee80211_set_mac_address; - if (register_netdev(dev)) { - kfree(dev); - return NULL; - } - local->wdev = dev; local->mdev = mdev; - ieee80211_precalc_rates(hw); - local->hw = hw; local->rx_handlers = ieee80211_rx_handlers; local->tx_handlers = ieee80211_tx_handlers; local->bridge_packets = 1; - local->curr_rates = hw->modes[0].rates; - local->num_curr_rates = hw->modes[0].num_rates; - ieee80211_prepare_rates(dev); - local->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; local->fragmentation_threshold = IEEE80211_MAX_FRAG_THRESHOLD; local->short_retry_limit = 7; @@ -4542,8 +4514,6 @@ struct net_device * ieee80211_register_h apdev->hard_header_parse = header_parse_80211; apdev->tx_queue_len = 0; sprintf(apdev->name, "%sap", dev->name); - if (register_netdev(apdev)) - goto fail; sdata = IEEE80211_DEV_TO_SUB_IF(apdev); sdata->type = IEEE80211_SUB_IF_TYPE_MGMT; @@ -4553,7 +4523,6 @@ struct net_device * ieee80211_register_h list_add_tail(&sdata->list, &local->sub_if_list); ether_setup(mdev); - mdev->priv = local; mdev->hard_start_xmit = ieee80211_master_start_xmit; mdev->do_ioctl = ieee80211_ioctl; mdev->change_mtu = ieee80211_change_mtu; @@ -4563,13 +4532,7 @@ struct net_device * ieee80211_register_h mdev->stop = ieee80211_stop; mdev->type = ARPHRD_IEEE80211; mdev->hard_header_parse = header_parse_80211; - if (hw->fraglist) - mdev->features |= NETIF_F_FRAGLIST; sprintf(mdev->name, "%s.11", dev->name); - if (register_netdev(mdev)) - goto fail; - /* TODO: add rtnl locking around device creation and qdisc install */ - ieee80211_install_qdisc(mdev); sdata = IEEE80211_DEV_TO_SUB_IF(mdev); sdata->type = IEEE80211_SUB_IF_TYPE_NORM; @@ -4578,47 +4541,97 @@ struct net_device * ieee80211_register_h sdata->local = local; list_add_tail(&sdata->list, &local->sub_if_list); - ieee80211_wep_init(local); - ieee80211_proc_init_interface(local); tasklet_init(&local->tasklet, ieee80211_tasklet_handler, (unsigned long) local); skb_queue_head_init(&local->skb_queue); skb_queue_head_init(&local->skb_queue_unreliable); - if (rate_control_initialize(local) < 0) { - printk(KERN_DEBUG "%s: Failed to initialize rate control " - "algorithm\n", dev->name); - goto fail; - } + if (setup) + setup(mdev); return mdev; fail: - ieee80211_unregister_hw(mdev); + ieee80211_free_hw(mdev); return NULL; } +int ieee80211_register_hw(struct net_device *dev, struct ieee80211_hw *hw) +{ + struct ieee80211_local *local = dev->priv; + int result; + if (!hw) + return -1; + if (hw->version != IEEE80211_VERSION) { + printk("ieee80211_register_hw - version mismatch: 80211.o " + "version %d, low-level driver version %d\n", + IEEE80211_VERSION, hw->version); + return -1; + } + + local->conf.mode = IW_MODE_MASTER; + local->conf.beacon_int = 1000; -void ieee80211_hw_initialized(struct net_device *dev, struct ieee80211_hw *hw) + ieee80211_update_hw(dev, hw); /* Don't care about the result. */ + + sta_info_start(local); + + result = register_netdev(local->wdev); + if (result < 0) + return -1; + + result = register_netdev(local->apdev); + if (result < 0) + goto fail_2nd_dev; + + if (hw->fraglist) + dev->features |= NETIF_F_FRAGLIST; + result = register_netdev(dev); + if (result < 0) + goto fail_3rd_dev; + + if (rate_control_initialize(local) < 0) { + printk(KERN_DEBUG "%s: Failed to initialize rate control " + "algorithm\n", dev->name); + goto fail_rate; + } + + /* TODO: add rtnl locking around device creation and qdisc install */ + ieee80211_install_qdisc(dev); + + ieee80211_wep_init(local); + ieee80211_proc_init_interface(local); + return 0; + +fail_rate: + unregister_netdev(dev); +fail_3rd_dev: + unregister_netdev(local->apdev); +fail_2nd_dev: + unregister_netdev(local->wdev); + sta_info_stop(local); + return result; +} + +int ieee80211_update_hw(struct net_device *dev, struct ieee80211_hw *hw) { - struct ieee80211_local *local = dev->priv; - struct net_device *apdev = local->apdev; + struct ieee80211_local *local = dev->priv; + + local->hw = hw; /* Backwards compatibility for low-level drivers that do not set number * of TX queues. */ if (hw->queues == 0) hw->queues = 1; - ieee80211_precalc_rates(hw); - local->hw = hw; - memcpy(apdev->dev_addr, dev->dev_addr, ETH_ALEN); - apdev->base_addr = dev->base_addr; - apdev->irq = dev->irq; - apdev->mem_start = dev->mem_start; - apdev->mem_end = dev->mem_end; + memcpy(local->apdev->dev_addr, dev->dev_addr, ETH_ALEN); + local->apdev->base_addr = dev->base_addr; + local->apdev->irq = dev->irq; + local->apdev->mem_start = dev->mem_start; + local->apdev->mem_end = dev->mem_end; memcpy(local->wdev->dev_addr, dev->dev_addr, ETH_ALEN); local->wdev->base_addr = dev->base_addr; @@ -4626,23 +4639,24 @@ void ieee80211_hw_initialized(struct net local->wdev->mem_start = dev->mem_start; local->wdev->mem_end = dev->mem_end; - local->conf.mode = IW_MODE_MASTER; - if (local->hw->num_modes > 0) { - local->conf.phymode = local->hw->modes[0].mode; - local->curr_rates = local->hw->modes[0].rates; - local->num_curr_rates = local->hw->modes[0].num_rates; - ieee80211_prepare_rates(dev); - } - if (local->hw->num_modes > 0 && local->hw->modes[0].num_channels > 0) { - local->conf.freq = local->hw->modes[0].channels[0].freq; - local->conf.channel = local->hw->modes[0].channels[0].chan; - } - local->conf.beacon_int = 1000; + if (!hw->modes || !hw->modes->channels || !hw->modes->rates || + !hw->modes->num_channels || !hw->modes->num_rates) + return -1; -} + ieee80211_precalc_rates(hw); + local->conf.phymode = hw->modes[0].mode; + local->curr_rates = hw->modes[0].rates; + local->num_curr_rates = hw->modes[0].num_rates; + ieee80211_prepare_rates(dev); + + local->conf.freq = local->hw->modes[0].channels[0].freq; + local->conf.channel = local->hw->modes[0].channels[0].chan; + /* FIXME: Invoke config to allow driver to set the channel. */ + return 0; +} -int ieee80211_unregister_hw(struct net_device *dev) +void ieee80211_unregister_hw(struct net_device *dev) { struct ieee80211_local *local = dev->priv; struct list_head *ptr, *n; @@ -4665,7 +4679,7 @@ int ieee80211_unregister_hw(struct net_d ieee80211_if_del(local, sdata, 0); } - sta_info_deinit(local); + sta_info_stop(local); for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++) if (local->fragments[i].skb) @@ -4685,12 +4699,17 @@ int ieee80211_unregister_hw(struct net_d skb_queue_purge(&local->skb_queue_unreliable); rate_control_free(local); +} +void ieee80211_free_hw(struct net_device *dev) +{ + struct ieee80211_local *local = dev->priv; + + kfree(local->sta_devs); + kfree(local->bss_devs); kfree(dev); - return 0; } - /* Perform netif operations on all configured interfaces */ int ieee80211_netif_oper(struct net_device *sdev, Netif_Oper op) { @@ -4845,9 +4864,11 @@ static void __exit ieee80211_exit(void) } +EXPORT_SYMBOL(ieee80211_alloc_hw); EXPORT_SYMBOL(ieee80211_register_hw); +EXPORT_SYMBOL(ieee80211_update_hw); EXPORT_SYMBOL(ieee80211_unregister_hw); -EXPORT_SYMBOL(ieee80211_hw_initialized); +EXPORT_SYMBOL(ieee80211_free_hw); EXPORT_SYMBOL(ieee80211_rx); EXPORT_SYMBOL(ieee80211_tx_status); EXPORT_SYMBOL(ieee80211_beacon_get); Index: netdev/net/ieee80211/sta_info.c =================================================================== --- netdev.orig/net/ieee80211/sta_info.c 2005-12-21 19:48:06.000000000 +0100 +++ netdev/net/ieee80211/sta_info.c 2005-12-21 19:48:34.000000000 +0100 @@ -356,12 +356,16 @@ void sta_info_init(struct ieee80211_loca local->sta_cleanup.expires = jiffies + STA_INFO_CLEANUP_INTERVAL; local->sta_cleanup.data = (unsigned long) local; local->sta_cleanup.function = sta_info_cleanup; - add_timer(&local->sta_cleanup); INIT_WORK(&local->sta_proc_add, sta_info_proc_add_task, local); } -void sta_info_deinit(struct ieee80211_local *local) +void sta_info_start(struct ieee80211_local *local) +{ + add_timer(&local->sta_cleanup); +} + +void sta_info_stop(struct ieee80211_local *local) { struct list_head *ptr; Index: netdev/net/ieee80211/sta_info.h =================================================================== --- netdev.orig/net/ieee80211/sta_info.h 2005-12-21 19:48:06.000000000 +0100 +++ netdev/net/ieee80211/sta_info.h 2005-12-21 19:48:34.000000000 +0100 @@ -134,7 +134,8 @@ struct sta_info * sta_info_add(struct ie void sta_info_free(struct ieee80211_local *local, struct sta_info *sta, int locked); void sta_info_init(struct ieee80211_local *local); -void sta_info_deinit(struct ieee80211_local *local); +void sta_info_start(struct ieee80211_local *local); +void sta_info_stop(struct ieee80211_local *local); void sta_info_remove_aid_ptr(struct sta_info *sta); void sta_info_flush(struct ieee80211_local *local, struct net_device *dev);