All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC] p54: software hot-swap eeprom image
@ 2009-11-20 19:27 Christian Lamparter
  2009-11-20 19:53 ` Dan Williams
  2009-11-20 20:24 ` Kalle Valo
  0 siblings, 2 replies; 10+ messages in thread
From: Christian Lamparter @ 2009-11-20 19:27 UTC (permalink / raw)
  To: linux-wireless

This (aggregated) patch adds a new sysfs file "eeprom_overwrite"
which can be used to upload an customized eeprom image to the
driver during normal operation.

The old wlanX device will quickly vanish. And if the new EEPROM
image passes all sanity checks the device will reappear.

Hopefully, this proves to be an adequate solution for everyone
with a bad/missing EEPROM data/chip.
---
there are plenty of bugs left!

would be nice to hear from p54spi & p54pci folks!

Regards,
	Chr
---
diff --git a/drivers/net/wireless/p54/eeprom.c b/drivers/net/wireless/p54/eeprom.c
index 8e3818f..e5c94d9 100644
--- a/drivers/net/wireless/p54/eeprom.c
+++ b/drivers/net/wireless/p54/eeprom.c
@@ -121,25 +121,20 @@ static int p54_generate_band(struct ieee80211_hw *dev,
 			     enum ieee80211_band band)
 {
 	struct p54_common *priv = dev->priv;
-	struct ieee80211_supported_band *tmp, *old;
+	struct ieee80211_supported_band *old_band, *new_band;
+	struct ieee80211_channel *tmp, *old_chan, *new_chan;
 	unsigned int i, j;
-	int ret = -ENOMEM;
+	int old_chan_num, ret = 0;
 
 	if ((!list->entries) || (!list->band_channel_num[band]))
 		return -EINVAL;
 
-	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
-	if (!tmp)
-		goto err_out;
-
-	tmp->channels = kzalloc(sizeof(struct ieee80211_channel) *
-				list->band_channel_num[band], GFP_KERNEL);
-	if (!tmp->channels)
-		goto err_out;
-
-	ret = p54_fill_band_bitrates(dev, tmp, band);
-	if (ret)
-		goto err_out;
+	tmp = kzalloc(sizeof(struct ieee80211_channel) *
+		list->band_channel_num[band], GFP_KERNEL);
+	if (!tmp) {
+		ret = -ENOMEM;
+		goto out;
+	}
 
 	for (i = 0, j = 0; (j < list->band_channel_num[band]) &&
 			   (i < list->entries); i++) {
@@ -161,8 +156,8 @@ static int p54_generate_band(struct ieee80211_hw *dev,
 			continue;
 		}
 
-		tmp->channels[j].band = list->channels[i].band;
-		tmp->channels[j].center_freq = list->channels[i].freq;
+		tmp[j].band = list->channels[i].band;
+		tmp[j].center_freq = list->channels[i].freq;
 		j++;
 	}
 
@@ -172,25 +167,60 @@ static int p54_generate_band(struct ieee80211_hw *dev,
 		       "2 GHz" : "5 GHz");
 
 		ret = -ENODATA;
-		goto err_out;
+		goto out;
 	}
 
-	tmp->n_channels = j;
-	old = priv->band_table[band];
-	priv->band_table[band] = tmp;
-	if (old) {
-		kfree(old->channels);
-		kfree(old);
+	old_band = priv->band_table[band];
+	if (old_band) {
+		old_chan_num = old_band->n_channels;
+		old_chan = old_band->channels;
+		old_band->n_channels = 0;
+		old_band->n_bitrates = 0;
+	} else {
+		old_chan_num = 0;
+		old_chan = NULL;
 	}
 
-	return 0;
+	if (j < old_chan_num) {
+		printk(KERN_ERR "%s: Channel list can only be extended.\n",
+		       wiphy_name(dev->wiphy));
+		goto out;
+	}
+
+	new_band = krealloc(old_band, sizeof(*new_band), GFP_KERNEL);
+	if (!new_band) {
+		printk(KERN_ERR "%s: Unable to modify band table.\n",
+		       wiphy_name(dev->wiphy));
+
+		goto out;
+	}
+
+	new_band->band = band;
+	new_band->ht_cap.ht_supported = false;
 
-err_out:
-	if (tmp) {
-		kfree(tmp->channels);
-		kfree(tmp);
+	new_chan = krealloc(old_chan, j * sizeof(struct ieee80211_channel),
+			    GFP_KERNEL);
+
+	if (!new_chan) {
+		printk(KERN_ERR "%s: Unable to modify band channel table.\n",
+		       wiphy_name(dev->wiphy));
+
+		goto out;
 	}
 
+	memcpy(new_chan, tmp, j * sizeof(struct ieee80211_channel));
+
+	new_band->channels = new_chan;
+	new_band->n_channels = j;
+
+	ret = p54_fill_band_bitrates(dev, new_band, band);
+	if (ret)
+		goto out;
+
+	priv->band_table[band] = new_band;
+
+out:
+	kfree(tmp);
 	return ret;
 }
 
@@ -533,7 +563,19 @@ static struct p54_cal_database *p54_convert_db(struct pda_custom_wrapper *src,
 	return dst;
 }
 
-int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
+static void p54_reset_phydata(struct p54_common *priv)
+{
+	memset(&priv->rssical_db, 0, sizeof(priv->rssical_db));
+	priv->iq_autocal_len = 0;
+	kfree(priv->output_limit);
+	kfree(priv->curve_data);
+	kfree(priv->iq_autocal);
+	priv->output_limit = NULL;
+	priv->curve_data = NULL;
+	priv->iq_autocal = NULL;
+}
+
+int p54_parse_eeprom(struct ieee80211_hw *dev, const void *eeprom, int len)
 {
 	struct p54_common *priv = dev->priv;
 	struct eeprom_pda_wrap *wrap;
@@ -543,10 +585,14 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 	int err;
 	u8 *end = (u8 *)eeprom + len;
 	u16 synth = 0;
+	bool has_rssical = false, has_mac = false, has_country = false;
 
 	wrap = (struct eeprom_pda_wrap *) eeprom;
 	entry = (void *)wrap->data + le16_to_cpu(wrap->len);
 
+	mutex_lock(&priv->conf_mutex);
+	p54_reset_phydata(priv);
+
 	/* verify that at least the entry length/code fits */
 	while ((u8 *)entry <= end - sizeof(*entry)) {
 		entry_len = le16_to_cpu(entry->len);
@@ -558,21 +604,34 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 
 		switch (le16_to_cpu(entry->code)) {
 		case PDR_MAC_ADDRESS:
+			if (has_mac)
+				break;
+
+			has_mac = true;
+
 			if (data_len != ETH_ALEN)
 				break;
+
 			SET_IEEE80211_PERM_ADDR(dev, entry->data);
 			break;
+
 		case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS:
 			if (priv->output_limit)
 				break;
+
 			err = p54_convert_output_limits(dev, entry->data,
 							data_len);
 			if (err)
 				goto err;
 			break;
+
 		case PDR_PRISM_PA_CAL_CURVE_DATA: {
 			struct pda_pa_curve_data *curve_data =
 				(struct pda_pa_curve_data *)entry->data;
+
+			if (priv->curve_data)
+				break;
+
 			if (data_len < sizeof(*curve_data)) {
 				err = -EINVAL;
 				goto err;
@@ -597,7 +656,11 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 				goto err;
 			}
 			break;
+
 		case PDR_PRISM_ZIF_TX_IQ_CALIBRATION:
+			if (priv->iq_autocal)
+				break;
+
 			priv->iq_autocal = kmalloc(data_len, GFP_KERNEL);
 			if (!priv->iq_autocal) {
 				err = -ENOMEM;
@@ -607,11 +670,22 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 			memcpy(priv->iq_autocal, entry->data, data_len);
 			priv->iq_autocal_len = data_len / sizeof(struct pda_iq_autocal_entry);
 			break;
+
 		case PDR_DEFAULT_COUNTRY:
+			if (has_country)
+				break;
+
+			has_country = true;
+
 			p54_parse_default_country(dev, entry->data, data_len);
 			break;
+
 		case PDR_INTERFACE_LIST:
 			tmp = entry->data;
+
+			if (synth)
+				break;
+
 			while ((u8 *)tmp < entry->data + data_len) {
 				struct exp_if *exp_if = tmp;
 				if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000))
@@ -619,22 +693,38 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 				tmp += sizeof(*exp_if);
 			}
 			break;
+
 		case PDR_HARDWARE_PLATFORM_COMPONENT_ID:
+			if (priv->version)
+				break;
+
 			if (data_len < 2)
 				break;
 			priv->version = *(u8 *)(entry->data + 1);
 			break;
+
 		case PDR_RSSI_LINEAR_APPROXIMATION:
 		case PDR_RSSI_LINEAR_APPROXIMATION_DUAL_BAND:
 		case PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED:
+			if (has_rssical)
+				break;
+
+			has_rssical = true;
+
 			p54_parse_rssical(dev, entry->data, data_len,
 					  le16_to_cpu(entry->code));
 			break;
+
 		case PDR_RSSI_LINEAR_APPROXIMATION_CUSTOM: {
 			__le16 *src = (void *) entry->data;
 			s16 *dst = (void *) &priv->rssical_db;
 			int i;
 
+			if (has_rssical)
+				break;
+
+			has_rssical = true;
+
 			if (data_len != sizeof(priv->rssical_db)) {
 				err = -EINVAL;
 				goto err;
@@ -644,24 +734,30 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 				*(dst++) = (s16) le16_to_cpu(*(src++));
 			}
 			break;
+
 		case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM: {
 			struct pda_custom_wrapper *pda = (void *) entry->data;
 			if (priv->output_limit || data_len < sizeof(*pda))
 				break;
+
 			priv->output_limit = p54_convert_db(pda, data_len);
 			}
 			break;
+
 		case PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM: {
 			struct pda_custom_wrapper *pda = (void *) entry->data;
 			if (priv->curve_data || data_len < sizeof(*pda))
 				break;
+
 			priv->curve_data = p54_convert_db(pda, data_len);
 			}
 			break;
+
 		case PDR_END:
 			/* make it overrun */
 			entry_len = len;
 			break;
+
 		default:
 			break;
 		}
@@ -670,7 +766,11 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 	}
 
 	if (!synth || !priv->iq_autocal || !priv->output_limit ||
-	    !priv->curve_data) {
+	    !priv->curve_data || !has_rssical) {
+		printk(KERN_INFO "%d %p %p %p %d\n",
+			synth, priv->iq_autocal, priv->output_limit,
+			priv->curve_data, has_rssical);
+
 		printk(KERN_ERR "%s: not all required entries found in eeprom!\n",
 			wiphy_name(dev->wiphy));
 		err = -EINVAL;
@@ -708,18 +808,18 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
 		wiphy_name(dev->wiphy),	dev->wiphy->perm_addr, priv->version,
 		p54_rf_chips[priv->rxhw]);
 
+	mutex_unlock(&priv->conf_mutex);
+
 	return 0;
 
 err:
-	kfree(priv->iq_autocal);
-	kfree(priv->output_limit);
-	kfree(priv->curve_data);
-	priv->iq_autocal = NULL;
-	priv->output_limit = NULL;
-	priv->curve_data = NULL;
+	p54_reset_phydata(priv);
 
 	printk(KERN_ERR "%s: eeprom parse failed!\n",
 		wiphy_name(dev->wiphy));
+
+	mutex_unlock(&priv->conf_mutex);
+
 	return err;
 }
 EXPORT_SYMBOL_GPL(p54_parse_eeprom);
@@ -753,8 +853,39 @@ int p54_read_eeprom(struct ieee80211_hw *dev)
 	}
 
 	ret = p54_parse_eeprom(dev, eeprom, offset);
+
 free:
 	kfree(eeprom);
 	return ret;
 }
 EXPORT_SYMBOL_GPL(p54_read_eeprom);
+
+ssize_t p54_overwrite_eeprom(struct ieee80211_hw *dev,
+			     const char *buf, size_t len)
+{
+	struct p54_common *priv = dev->priv;
+	int err;
+
+	if (len < 64 || len > 0x2020)
+		return -EINVAL;
+
+	mutex_lock(&priv->sysfs_mutex);
+	p54_unregister_common(dev);
+
+	err = p54_parse_eeprom(dev, buf, len);
+	if (err)
+		goto out;
+
+	err = p54_register_common(dev, priv->pdev);
+	if (err)
+		goto out;
+
+out:
+
+	if (err)
+		dev_err(priv->pdev, "failed to (re)-register device.\n");
+
+	mutex_unlock(&priv->sysfs_mutex);
+	return (err) ? err : len;
+}
+EXPORT_SYMBOL_GPL(p54_overwrite_eeprom);
diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c
index 18012db..e1395d4 100644
--- a/drivers/net/wireless/p54/main.c
+++ b/drivers/net/wireless/p54/main.c
@@ -582,6 +582,7 @@ struct ieee80211_hw *p54_init_common(size_t priv_data_len)
 	dev->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
 
 	mutex_init(&priv->conf_mutex);
+	mutex_init(&priv->sysfs_mutex);
 	mutex_init(&priv->eeprom_mutex);
 	init_completion(&priv->eeprom_comp);
 	init_completion(&priv->beacon_comp);
@@ -596,6 +597,9 @@ int p54_register_common(struct ieee80211_hw *dev, struct device *pdev)
 	struct p54_common *priv = dev->priv;
 	int err;
 
+	if (priv->registered)
+		return -EBUSY;
+
 	err = ieee80211_register_hw(dev);
 	if (err) {
 		dev_err(pdev, "Cannot register device (%d).\n", err);
@@ -607,8 +611,9 @@ int p54_register_common(struct ieee80211_hw *dev, struct device *pdev)
 	if (err)
 		return err;
 #endif /* CONFIG_P54_LEDS */
-
+	priv->pdev = pdev;
 	dev_info(pdev, "is registered as '%s'\n", wiphy_name(dev->wiphy));
+	priv->registered = true;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(p54_register_common);
@@ -618,6 +623,11 @@ void p54_free_common(struct ieee80211_hw *dev)
 	struct p54_common *priv = dev->priv;
 	unsigned int i;
 
+	BUG_ON(priv->registered);
+
+	mutex_destroy(&priv->conf_mutex);
+	mutex_destroy(&priv->eeprom_mutex);
+
 	for (i = 0; i < IEEE80211_NUM_BANDS; i++)
 		kfree(priv->band_table[i]);
 
@@ -637,12 +647,14 @@ void p54_unregister_common(struct ieee80211_hw *dev)
 {
 	struct p54_common *priv = dev->priv;
 
+	if (!priv->registered)
+		return ;
+
 #ifdef CONFIG_P54_LEDS
 	p54_unregister_leds(priv);
 #endif /* CONFIG_P54_LEDS */
 
-	ieee80211_unregister_hw(dev);
-	mutex_destroy(&priv->conf_mutex);
-	mutex_destroy(&priv->eeprom_mutex);
+	ieee80211_unregister_hw(priv->hw);
+	priv->registered = false;
 }
 EXPORT_SYMBOL_GPL(p54_unregister_common);
diff --git a/drivers/net/wireless/p54/p54.h b/drivers/net/wireless/p54/p54.h
index 1afc394..3e097e0 100644
--- a/drivers/net/wireless/p54/p54.h
+++ b/drivers/net/wireless/p54/p54.h
@@ -159,6 +159,7 @@ struct p54_led_dev {
 
 struct p54_common {
 	struct ieee80211_hw *hw;
+	struct device *pdev;
 	struct ieee80211_vif *vif;
 	void (*tx)(struct ieee80211_hw *dev, struct sk_buff *skb);
 	int (*open)(struct ieee80211_hw *dev);
@@ -166,6 +167,7 @@ struct p54_common {
 	struct sk_buff_head tx_pending;
 	struct sk_buff_head tx_queue;
 	struct mutex conf_mutex;
+	bool registered;
 
 	/* memory management (as seen by the firmware) */
 	u32 rx_start;
@@ -233,14 +235,17 @@ struct p54_common {
 	void *eeprom;
 	struct completion eeprom_comp;
 	struct mutex eeprom_mutex;
+	struct mutex sysfs_mutex;
 };
 
 /* interfaces for the drivers */
 int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb);
 void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb);
 int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw);
-int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len);
+int p54_parse_eeprom(struct ieee80211_hw *dev, const void *eeprom, int len);
 int p54_read_eeprom(struct ieee80211_hw *dev);
+ssize_t p54_overwrite_eeprom(struct ieee80211_hw *dev,
+			    const char *eeprom, size_t len);
 
 struct ieee80211_hw *p54_init_common(size_t priv_data_len);
 int p54_register_common(struct ieee80211_hw *dev, struct device *pdev);
diff --git a/drivers/net/wireless/p54/p54pci.c b/drivers/net/wireless/p54/p54pci.c
index e3a4c90..b8040e4 100644
--- a/drivers/net/wireless/p54/p54pci.c
+++ b/drivers/net/wireless/p54/p54pci.c
@@ -466,6 +466,18 @@ static int p54p_open(struct ieee80211_hw *dev)
 	return 0;
 }
 
+static ssize_t p54p_sysfs_overwrite_eeprom(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf, size_t count)
+{
+	struct p54p_priv *priv = dev_get_drvdata(dev);
+
+	return p54_overwrite_eeprom(priv->common.hw, buf, count);
+}
+
+static DEVICE_ATTR(eeprom_overwrite, S_IWUSR,
+		   NULL, p54p_sysfs_overwrite_eeprom);
+
 static int __devinit p54p_probe(struct pci_dev *pdev,
 				const struct pci_device_id *id)
 {
@@ -561,6 +573,13 @@ static int __devinit p54p_probe(struct pci_dev *pdev,
 	if (err)
 		goto err_free_common;
 
+	err = device_create_file(&pdev->dev, &dev_attr_eeprom_overwrite);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs file "
+				    "eeprom_overwrite.\n");
+		goto err_free_common;
+	}
+
 	return 0;
 
  err_free_common:
@@ -591,6 +610,8 @@ static void __devexit p54p_remove(struct pci_dev *pdev)
 		return;
 
 	p54_unregister_common(dev);
+	device_remove_file(&pdev->dev, &dev_attr_eeprom_overwrite);
+
 	priv = dev->priv;
 	release_firmware(priv->firmware);
 	pci_free_consistent(pdev, sizeof(*priv->ring_control),
diff --git a/drivers/net/wireless/p54/p54spi.c b/drivers/net/wireless/p54/p54spi.c
index afd26bf..6fb0863 100644
--- a/drivers/net/wireless/p54/p54spi.c
+++ b/drivers/net/wireless/p54/p54spi.c
@@ -592,6 +592,18 @@ static void p54spi_op_stop(struct ieee80211_hw *dev)
 	mutex_unlock(&priv->mutex);
 }
 
+static ssize_t p54s_sysfs_overwrite_eeprom(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf, size_t count)
+{
+	struct p54s_priv *priv = dev_get_drvdata(dev);
+
+	return p54_overwrite_eeprom(priv->common.hw, buf, count);
+}
+
+static DEVICE_ATTR(eeprom_overwrite, S_IWUSR,
+		   NULL, p54s_sysfs_overwrite_eeprom);
+
 static int __devinit p54spi_probe(struct spi_device *spi)
 {
 	struct p54s_priv *priv = NULL;
@@ -667,7 +679,9 @@ static int __devinit p54spi_probe(struct spi_device *spi)
 	if (ret)
 		goto err_free_common;
 
-	return 0;
+        ret = device_create_file(&spi->dev, &dev_attr_eeprom_overwrite);
+
+	return ret;
 
 err_free_common:
 	p54_free_common(priv->hw);
@@ -680,6 +694,9 @@ static int __devexit p54spi_remove(struct spi_device *spi)
 
 	p54_unregister_common(priv->hw);
 
+	device_remove_file(&spi->dev, &dev_attr_eeprom_overwrite);
+	dev_set_drvdata(&spi->dev, NULL);
+
 	free_irq(gpio_to_irq(p54spi_gpio_irq), spi);
 
 	gpio_free(p54spi_gpio_power);
diff --git a/drivers/net/wireless/p54/p54usb.c b/drivers/net/wireless/p54/p54usb.c
index 92af9b9..be699d6 100644
--- a/drivers/net/wireless/p54/p54usb.c
+++ b/drivers/net/wireless/p54/p54usb.c
@@ -874,6 +874,19 @@ static void p54u_stop(struct ieee80211_hw *dev)
 	return;
 }
 
+static ssize_t p54u_sysfs_overwrite_eeprom(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf, size_t count)
+{
+        struct ieee80211_hw *hw = (struct ieee80211_hw *)
+                usb_get_intfdata(to_usb_interface(dev));
+
+	return p54_overwrite_eeprom(hw, buf, count);
+}
+
+static DEVICE_ATTR(eeprom_overwrite, S_IWUSR,
+                   NULL, p54u_sysfs_overwrite_eeprom);
+
 static int __devinit p54u_probe(struct usb_interface *intf,
 				const struct usb_device_id *id)
 {
@@ -959,7 +972,9 @@ static int __devinit p54u_probe(struct usb_interface *intf,
 	if (err)
 		goto err_free_fw;
 
-	return 0;
+	err = device_create_file(&intf->dev, &dev_attr_eeprom_overwrite);
+
+	return err;
 
 err_free_fw:
 	release_firmware(priv->fw);
@@ -982,6 +997,9 @@ static void __devexit p54u_disconnect(struct usb_interface *intf)
 	p54_unregister_common(dev);
 
 	priv = dev->priv;
+	device_remove_file(&intf->dev, &dev_attr_eeprom_overwrite); 
+
+	usb_set_intfdata(intf, NULL);
 	usb_put_dev(interface_to_usbdev(intf));
 	release_firmware(priv->fw);
 	p54_free_common(dev);

^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2009-11-20 23:33 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-11-20 19:27 [RFC] p54: software hot-swap eeprom image Christian Lamparter
2009-11-20 19:53 ` Dan Williams
2009-11-20 20:32   ` Luis R. Rodriguez
2009-11-20 20:32     ` Luis R. Rodriguez
2009-11-20 20:32   ` Kalle Valo
2009-11-20 20:24 ` Kalle Valo
2009-11-20 20:48   ` Dan Williams
2009-11-20 21:02     ` Kalle Valo
2009-11-20 23:12       ` Dan Williams
2009-11-20 23:33   ` Christian Lamparter

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.