From: Neil Horman <nhorman@redhat.com> Date: Wed, 27 May 2009 15:42:21 -0400 Subject: [net] sky2: fix eeprom reads Message-id: 20090527194221.GZ19071@hmsreliant.think-freely.org O-Subject: Re: [RHEL 5.4 PATCH] sky2: Fix eeprom reads (bz 501050) Bugzilla: 501050 RH-Acked-by: Andy Gospodarek <gospo@redhat.com> On Wed, May 27, 2009 at 12:29:01PM -0400, Andy Gospodarek wrote: > On Wed, May 27, 2009 at 11:47:53AM -0400, Neil Horman wrote: > > Hey all- > > This is a backport of upstream commit > > 1413235c14301c4bd27aabf408e4336719b6f505 and supporting bits from > > e4c2abe29e1ec5d68908848ffa77b39f61a83f7c. They fix sky2s eeprom reading code, > > As it currently stands, the sky2 eeprom reading code can spin forever, and is > > able to write half words, which is both unnneeded and unsafe. In the sky2 card > > this mentioned in this bz, it seems to cause an internal hw error which results > > in no frames getting sent or received. This patch fixes that and will allow the > > system in question to continue their hwcert process. > > > > Neil diff --git a/drivers/net/sky2.c b/drivers/net/sky2.c index b8515b8..9a35837 100644 --- a/drivers/net/sky2.c +++ b/drivers/net/sky2.c @@ -3758,63 +3758,27 @@ static int sky2_get_eeprom_len(struct net_device *dev) return 1 << ( ((reg2 & PCI_VPD_ROM_SZ) >> 14) + 8); } -static int sky2_vpd_wait(const struct sky2_hw *hw, int cap, u16 busy) +static u32 sky2_vpd_read(struct sky2_hw *hw, int cap, u16 offset) { - unsigned long start = jiffies; + u32 val; - while ( (sky2_pci_read16(hw, cap + PCI_VPD_ADDR) & PCI_VPD_ADDR_F) == busy) { - /* Can take up to 10.6 ms for write */ - if (time_after(jiffies, start + HZ/4)) { - dev_err(&hw->pdev->dev, PFX "VPD cycle timed out"); - return -ETIMEDOUT; - } - mdelay(1); - } - - return 0; -} - -static int sky2_vpd_read(struct sky2_hw *hw, int cap, void *data, - u16 offset, size_t length) -{ - int rc = 0; - - while (length > 0) { - u32 val; - - sky2_pci_write16(hw, cap + PCI_VPD_ADDR, offset); - rc = sky2_vpd_wait(hw, cap, 0); - if (rc) - break; + sky2_pci_write16(hw, cap + PCI_VPD_ADDR, offset); - val = sky2_pci_read32(hw, cap + PCI_VPD_DATA); - - memcpy(data, &val, min(sizeof(val), length)); - offset += sizeof(u32); - data += sizeof(u32); - length -= sizeof(u32); - } + do { + offset = sky2_pci_read16(hw, cap + PCI_VPD_ADDR); + } while (!(offset & PCI_VPD_ADDR_F)); - return rc; + val = sky2_pci_read32(hw, cap + PCI_VPD_DATA); + return val; } -static int sky2_vpd_write(struct sky2_hw *hw, int cap, const void *data, - u16 offset, unsigned int length) +static void sky2_vpd_write(struct sky2_hw *hw, int cap, u16 offset, u32 val) { - unsigned int i; - int rc = 0; - - for (i = 0; i < length; i += sizeof(u32)) { - u32 val = *(u32 *)(data + i); - - sky2_pci_write32(hw, cap + PCI_VPD_DATA, val); - sky2_pci_write32(hw, cap + PCI_VPD_ADDR, offset | PCI_VPD_ADDR_F); - - rc = sky2_vpd_wait(hw, cap, PCI_VPD_ADDR_F); - if (rc) - break; - } - return rc; + sky2_pci_write16(hw, cap + PCI_VPD_DATA, val); + sky2_pci_write32(hw, cap + PCI_VPD_ADDR, offset | PCI_VPD_ADDR_F); + do { + offset = sky2_pci_read16(hw, cap + PCI_VPD_ADDR); + } while (offset & PCI_VPD_ADDR_F); } static int sky2_get_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom, @@ -3822,13 +3786,24 @@ static int sky2_get_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom { struct sky2_port *sky2 = netdev_priv(dev); int cap = pci_find_capability(sky2->hw->pdev, PCI_CAP_ID_VPD); + int length = eeprom->len; + u16 offset = eeprom->offset; if (!cap) return -EINVAL; eeprom->magic = SKY2_EEPROM_MAGIC; - return sky2_vpd_read(sky2->hw, cap, data, eeprom->offset, eeprom->len); + while (length > 0) { + u32 val = sky2_vpd_read(sky2->hw, cap, offset); + int n = min_t(int, length, sizeof(val)); + + memcpy(data, &val, n); + length -= n; + data += n; + offset += n; + } + return 0; } static int sky2_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom, @@ -3836,6 +3811,8 @@ static int sky2_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom { struct sky2_port *sky2 = netdev_priv(dev); int cap = pci_find_capability(sky2->hw->pdev, PCI_CAP_ID_VPD); + int length = eeprom->len; + u16 offset = eeprom->offset; if (!cap) return -EINVAL; @@ -3843,11 +3820,21 @@ static int sky2_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom if (eeprom->magic != SKY2_EEPROM_MAGIC) return -EINVAL; - /* Partial writes not supported */ - if ((eeprom->offset & 3) || (eeprom->len & 3)) - return -EINVAL; + while (length > 0) { + u32 val; + int n = min_t(int, length, sizeof(val)); + + if (n < sizeof(val)) + val = sky2_vpd_read(sky2->hw, cap, offset); + memcpy(&val, data, n); - return sky2_vpd_write(sky2->hw, cap, data, eeprom->offset, eeprom->len); + sky2_vpd_write(sky2->hw, cap, offset, val); + + length -= n; + data += n; + offset += n; + } + return 0; } @@ -4203,69 +4190,6 @@ static int __devinit sky2_test_msi(struct sky2_hw *hw) return err; } -/* - * Read and parse the first part of Vital Product Data - */ -#define VPD_SIZE 128 -#define VPD_MAGIC 0x82 - -static void __devinit sky2_vpd_info(struct sky2_hw *hw) -{ - int cap = pci_find_capability(hw->pdev, PCI_CAP_ID_VPD); - const u8 *p; - u8 *vpd_buf = NULL; - u16 len; - static struct vpd_tag { - char tag[2]; - char *label; - } vpd_tags[] = { - { "PN", "Part Number" }, - { "EC", "Engineering Level" }, - { "MN", "Manufacturer" }, - }; - - if (!cap) - goto out; - - vpd_buf = kmalloc(VPD_SIZE, GFP_KERNEL); - if (!vpd_buf) - goto out; - - if (sky2_vpd_read(hw, cap, vpd_buf, 0, VPD_SIZE)) - goto out; - - if (vpd_buf[0] != VPD_MAGIC) - goto out; - len = vpd_buf[1]; - if (len == 0 || len > VPD_SIZE - 4) - goto out; - p = vpd_buf + 3; - dev_info(&hw->pdev->dev, "%.*s\n", len, p); - p += len; - - while (p < vpd_buf + VPD_SIZE - 4) { - int i; - - if (!memcmp("RW", p, 2)) /* end marker */ - break; - - len = p[2]; - if (len > (p - vpd_buf) - 4) - break; - - for (i = 0; i < ARRAY_SIZE(vpd_tags); i++) { - if (!memcmp(vpd_tags[i].tag, p, 2)) { - printk(KERN_DEBUG " %s: %.*s\n", - vpd_tags[i].label, len, p + 3); - break; - } - } - p += len + 3; - } -out: - kfree(vpd_buf); -} - /* This driver supports yukon2 chipset only */ static const char *sky2_name(u8 chipid, char *buf, int sz) { @@ -4364,13 +4288,13 @@ static int __devinit sky2_probe(struct pci_dev *pdev, if (err) goto err_out_iounmap; - dev_info(&pdev->dev, "Yukon-2 %s chip revision %d\n", - sky2_name(hw->chip_id, buf1, sizeof(buf1)), hw->chip_rev); + dev_info(&pdev->dev, "v%s addr 0x%llx irq %d Yukon-2 %s rev %d\n", + DRV_VERSION, (unsigned long long)pci_resource_start(pdev, 0), + pdev->irq, sky2_name(hw->chip_id, buf1, sizeof(buf1)), + hw->chip_rev); sky2_reset(hw); - sky2_vpd_info(hw); - dev = sky2_init_netdev(hw, 0, using_dac, wol_default); if (!dev) { err = -ENOMEM;