This patch adds a generic mechanism for overriding the SPROM mechanism on devices without SPROM hardware. There currently is a major problem with this: It tries to deduce a MAC address from various hardware parameters. But currently it will result in the same MAC address for machines of the same type. Does somebody have an idea of some device-instance specific serial number or something similar that could be hashed into the MAC? Index: wireless-testing/drivers/ssb/pci.c =================================================================== --- wireless-testing.orig/drivers/ssb/pci.c 2009-11-19 18:34:42.000000000 +0100 +++ wireless-testing/drivers/ssb/pci.c 2009-11-19 18:37:27.000000000 +0100 @@ -252,6 +252,9 @@ static int sprom_do_read(struct ssb_bus { int i; + if (!bus->sprom_size) + return -ENODEV; + for (i = 0; i < bus->sprom_size; i++) sprom[i] = ioread16(bus->mmio + SSB_SPROM_BASE + (i * 2)); @@ -265,6 +268,9 @@ static int sprom_do_write(struct ssb_bus u32 spromctl; u16 size = bus->sprom_size; + if (!size) + return -ENODEV; + ssb_printk(KERN_NOTICE PFX "Writing SPROM. Do NOT turn off the power! Please stand by...\n"); err = pci_read_config_dword(pdev, SSB_SPROMCTL, &spromctl); if (err) @@ -616,10 +622,17 @@ static int sprom_extract(struct ssb_bus static int ssb_pci_sprom_get(struct ssb_bus *bus, struct ssb_sprom *sprom) { - const struct ssb_sprom *fallback; - int err = -ENOMEM; + int err; u16 *buf; + bus->sprom_size = 0; + err = ssb_find_sprom_override(bus, sprom); + if (!err) { + ssb_printk(KERN_INFO PFX "Overriding SPROM image\n"); + return 0; + } + + err = -ENOMEM; buf = kcalloc(SSB_SPROMSIZE_WORDS_R123, sizeof(u16), GFP_KERNEL); if (!buf) goto out; @@ -637,22 +650,12 @@ static int ssb_pci_sprom_get(struct ssb_ sprom_do_read(bus, buf); err = sprom_check_crc(buf, bus->sprom_size); if (err) { - /* All CRC attempts failed. - * Maybe there is no SPROM on the device? - * If we have a fallback, use that. */ - fallback = ssb_get_fallback_sprom(); - if (fallback) { - memcpy(sprom, fallback, sizeof(*sprom)); - err = 0; - goto out_free; - } ssb_printk(KERN_WARNING PFX "WARNING: Invalid" " SPROM CRC (corrupt SPROM)\n"); } } err = sprom_extract(bus, sprom, buf, bus->sprom_size); -out_free: kfree(buf); out: return err; Index: wireless-testing/drivers/ssb/sprom.c =================================================================== --- wireless-testing.orig/drivers/ssb/sprom.c 2009-11-19 18:34:42.000000000 +0100 +++ wireless-testing/drivers/ssb/sprom.c 2009-11-19 18:37:27.000000000 +0100 @@ -13,8 +13,13 @@ #include "ssb_private.h" +#include <linux/list.h> +#include <linux/spinlock.h> -static const struct ssb_sprom *fallback_sprom; + +/* List of registered SPROM overrides. */ +static LIST_HEAD(override_list); +static DEFINE_SPINLOCK(override_list_lock); static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len, @@ -135,35 +140,34 @@ out: return err ? err : count; } -/** - * ssb_arch_set_fallback_sprom - Set a fallback SPROM for use if no SPROM is found. - * - * @sprom: The SPROM data structure to register. - * - * With this function the architecture implementation may register a fallback - * SPROM data structure. The fallback is only used for PCI based SSB devices, - * where no valid SPROM can be found in the shadow registers. - * - * This function is useful for weird architectures that have a half-assed SSB device - * hardwired to their PCI bus. - * - * Note that it does only work with PCI attached SSB devices. PCMCIA devices currently - * don't use this fallback. - * Architectures must provide the SPROM for native SSB devices anyway, - * so the fallback also isn't used for native devices. - * - * This function is available for architecture code, only. So it is not exported. - */ -int ssb_arch_set_fallback_sprom(const struct ssb_sprom *sprom) -{ - if (fallback_sprom) - return -EEXIST; - fallback_sprom = sprom; +void ssb_register_sprom_override(struct ssb_sprom_override *ovr) +{ + spin_lock(&override_list_lock); + list_add_tail(&ovr->list, &override_list); + spin_unlock(&override_list_lock); +} +EXPORT_SYMBOL(ssb_register_sprom_override); - return 0; +void ssb_unregister_sprom_override(struct ssb_sprom_override *ovr) +{ + spin_lock(&override_list_lock); + list_del(&ovr->list); + spin_unlock(&override_list_lock); } +EXPORT_SYMBOL(ssb_unregister_sprom_override); -const struct ssb_sprom *ssb_get_fallback_sprom(void) +int ssb_find_sprom_override(struct ssb_bus *bus, struct ssb_sprom *buf) { - return fallback_sprom; + struct ssb_sprom_override *ovr; + int err = -ENODEV; + + spin_lock(&override_list_lock); + list_for_each_entry(ovr, &override_list, list) { + err = ovr->probe(bus, buf); + if (!err) + break; + } + spin_unlock(&override_list_lock); + + return err; } Index: wireless-testing/drivers/ssb/ssb_private.h =================================================================== --- wireless-testing.orig/drivers/ssb/ssb_private.h 2009-11-19 18:34:42.000000000 +0100 +++ wireless-testing/drivers/ssb/ssb_private.h 2009-11-19 18:37:27.000000000 +0100 @@ -171,7 +171,7 @@ ssize_t ssb_attr_sprom_store(struct ssb_ const char *buf, size_t count, int (*sprom_check_crc)(const u16 *sprom, size_t size), int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom)); -extern const struct ssb_sprom *ssb_get_fallback_sprom(void); +extern int ssb_find_sprom_override(struct ssb_bus *bus, struct ssb_sprom *buf); /* core.c */ Index: wireless-testing/include/linux/ssb/ssb.h =================================================================== --- wireless-testing.orig/include/linux/ssb/ssb.h 2009-11-19 18:34:42.000000000 +0100 +++ wireless-testing/include/linux/ssb/ssb.h 2009-11-19 18:37:27.000000000 +0100 @@ -394,9 +394,20 @@ extern int ssb_bus_sdiobus_register(stru extern void ssb_bus_unregister(struct ssb_bus *bus); -/* Set a fallback SPROM. - * See kdoc at the function definition for complete documentation. */ -extern int ssb_arch_set_fallback_sprom(const struct ssb_sprom *sprom); +/** struct ssb_sprom_override - SPROM override handler + * @probe: Callback function used to probe for a SPROM override. + * Puts the override image into "buf" and returns 0. + * If there's no need to override the image, nonzero is returned. + * This callback runs in atomic context. + * @list: Used internally in ssb. Do not use in the device driver. + */ +struct ssb_sprom_override { + int (*probe)(struct ssb_bus *bus, struct ssb_sprom *buf); + struct list_head list; +}; + +extern void ssb_register_sprom_override(struct ssb_sprom_override *ovr); +extern void ssb_unregister_sprom_override(struct ssb_sprom_override *ovr); /* Suspend a SSB bus. * Call this from the parent bus suspend routine. */ Index: wireless-testing/drivers/ssb/b43_pci_bridge.c =================================================================== --- wireless-testing.orig/drivers/ssb/b43_pci_bridge.c 2009-11-19 18:34:42.000000000 +0100 +++ wireless-testing/drivers/ssb/b43_pci_bridge.c 2009-11-20 12:04:09.000000000 +0100 @@ -5,13 +5,15 @@ * because of its small size we include it in the SSB core * instead of creating a standalone module. * - * Copyright 2007 Michael Buesch <mb@xxxxxxxxx> + * Copyright 2007-2009 Michael Buesch <mb@xxxxxxxxx> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/pci.h> #include <linux/ssb/ssb.h> +#include <linux/etherdevice.h> +#include <linux/jhash.h> #include "ssb_private.h" @@ -36,6 +38,76 @@ static const struct pci_device_id b43_pc }; MODULE_DEVICE_TABLE(pci, b43_pci_bridge_tbl); + +static void pcidev_deduce_mac_address(struct pci_dev *pdev, + struct ssb_sprom *sprom, + const char *oui) +{ + u32 hash = 0x63E72B6D; + + hash = jhash(&pdev->device, sizeof(pdev->device), hash); + hash = jhash(&pdev->subsystem_device, sizeof(pdev->subsystem_device), hash); + hash = jhash(&pdev->devfn, sizeof(pdev->devfn), hash); + //TODO: Need machine specific seed + + sprom->il0mac[3] = hash; + sprom->il0mac[4] = hash >> 8; + sprom->il0mac[5] = hash >> 16; + memcpy(sprom->il0mac, oui, 3); + memcpy(sprom->et0mac, sprom->il0mac, ETH_ALEN); + memcpy(sprom->et1mac, sprom->il0mac, ETH_ALEN); +} + +#define IS_PDEV(pdev, _vendor, _device, _subvendor, _subdevice) ( \ + (pdev->vendor == PCI_VENDOR_ID_##_vendor) && \ + (pdev->device == _device) && \ + (pdev->subsystem_vendor == PCI_VENDOR_ID_##_subvendor) && \ + (pdev->subsystem_device == _subdevice) ) + +static int b43_sprom_override_probe(struct ssb_bus *bus, + struct ssb_sprom *sprom) +{ + struct pci_dev *pdev; + + if (bus->bustype != SSB_BUSTYPE_PCI) + return -ENODEV; + pdev = bus->host_pci; + + if (IS_PDEV(pdev, BROADCOM, 0x4315, FOXCONN, 0xE01B)) { + static const struct ssb_sprom image = { + .revision = 0x02, + .board_rev = 0x17, + .country_code = 0x0, + .ant_available_bg = 0x3, + .pa0b0 = 0x15ae, + .pa0b1 = 0xfa85, + .pa0b2 = 0xfe8d, + .pa1b0 = 0xffff, + .pa1b1 = 0xffff, + .pa1b2 = 0xffff, + .gpio0 = 0xff, + .gpio1 = 0xff, + .gpio2 = 0xff, + .gpio3 = 0xff, + .maxpwr_bg = 0x004c, + .itssi_bg = 0x00, + .boardflags_lo = 0x2848, + .boardflags_hi = 0x0000, + };//FIXME This image is not the right one. + + memcpy(sprom, &image, sizeof(*sprom)); + pcidev_deduce_mac_address(pdev, sprom, "\x00\x15\x58"); + + return 0; + } + + return -ENODEV; +} + +static struct ssb_sprom_override b43_sprom_override = { + .probe = b43_sprom_override_probe, +}; + static struct pci_driver b43_pci_bridge_driver = { .name = "b43-pci-bridge", .id_table = b43_pci_bridge_tbl, @@ -44,10 +116,20 @@ static struct pci_driver b43_pci_bridge_ int __init b43_pci_ssb_bridge_init(void) { - return ssb_pcihost_register(&b43_pci_bridge_driver); + int err; + + ssb_register_sprom_override(&b43_sprom_override); + err = ssb_pcihost_register(&b43_pci_bridge_driver); + if (err) { + ssb_unregister_sprom_override(&b43_sprom_override); + return err; + } + + return 0; } void __exit b43_pci_ssb_bridge_exit(void) { ssb_pcihost_unregister(&b43_pci_bridge_driver); + ssb_unregister_sprom_override(&b43_sprom_override); } -- Greetings, Michael. -- To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html