Re: [PATCHv2] pci: Update VPD size with correct length

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Dec 16, 2015 at 2:49 AM, Hannes Reinecke <hare@xxxxxxx> wrote:
> PCI-2.2 VPD entries have a maximum size of 32k, but might actually
> be smaller than that. To figure out the actual size one has to read
> the VPD area until the 'end marker' is reached.
> Trying to read VPD data beyond that marker results in 'interesting'
> effects, from simple read errors to crashing the card. And to make
> matters worse not every PCI card implements this properly, leaving
> us with no 'end' marker or even completely invalid data.
> This path modifies the size of the VPD attribute to the available
> size, and disables the VPD attribute altogether if no valid data
> could be read.
>
> Cc: Alexander Duyck <alexander.duyck@xxxxxxxxx>
> Cc: Michal Kubecek <mkubecek@xxxxxxxx>
> Signed-off-by: Hannes Reinecke <hare@xxxxxxx>
> ---
>  drivers/pci/access.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 49 insertions(+)
>
> diff --git a/drivers/pci/access.c b/drivers/pci/access.c
> index 59ac36f..ab571a5 100644
> --- a/drivers/pci/access.c
> +++ b/drivers/pci/access.c
> @@ -475,6 +475,48 @@ static const struct pci_vpd_ops pci_vpd_f0_ops = {
>         .release = pci_vpd_pci22_release,
>  };
>
> +/**
> + * pci_vpd_size - determine actual size of Vital Product Data
> + * @dev:       pci device struct
> + * @old_size:  current assumed size, also maximum allowed size
> + *
> + */
> +static size_t
> +pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size)
> +{

Instead of passing old_size you could look at just using
PCI_VPD_PCI22_SIZE since currently your only caller will always be
passing that value anyway.

> +       size_t off = 0;
> +       unsigned char header[1+2];      /* 1 byte tag, 2 bytes length */
> +
> +       while (off < old_size && pci_read_vpd(dev, off, 1, header)) {
> +               unsigned char tag;
> +

I'm not sure the use of pci_read_vpd here is correct.  Doesn't it
return a non-zero value on error?  If so you should probably be
checking for this being greater than 0 instead of just non-zero
shouldn't you?

> +               if (header[0] == 0xff) {
> +                       /* Invalid data from VPD read */
> +                       tag = header[0];
> +               } else if (header[0] & 0x80) {
> +                       /* Large Resource Data Type Tag */
> +                       if (pci_read_vpd(dev, off+1, 2, &header[1]) != 2)
> +                               return off + 1;
> +                       off += 3 + ((header[2] << 8) | header[1]);
> +                       tag = (header[0] & 0x7f);
> +               } else {
> +                       /* Short Resource Data Type Tag */
> +                       off += 1 + (header[0] & 0x07);
> +                       tag = (header[0] & 0x78) >> 3;
> +               }
> +               if (tag == 0x0f)        /* End tag descriptor */
> +                       break;

It might make sense to just use the "return off" here since this is
the only spot that should be returning the offset.

> +               if ((tag != 0x02) && (tag != 0x10) && (tag != 0x11)) {
> +                       dev_dbg(&dev->dev,
> +                               "invalid %s vpd tag %02x at offset %zu.",
> +                               header[0] & 0x80 ? "large" : "short",
> +                               tag, off);
> +                       break;
> +               }

It might be worth while to convert some of the values used in your
conditional checks into a human readable format by converting some of
the magic numbers into defines or placing them in an enum.  Then it
makes it a bit more obvious that if the tag is not a VPD identifier
string descriptor, VPD-R descriptor, or VPD-W descriptor you should be
reporting an error.

> +       }
> +       return off;
> +}
> +

You could probably convert this to "return 0" since there is only one
spot above where you would be returning the offset from.

>  int pci_vpd_pci22_init(struct pci_dev *dev)
>  {
>         struct pci_vpd_pci22 *vpd;
> @@ -497,6 +539,13 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
>         vpd->cap = cap;
>         vpd->busy = false;
>         dev->vpd = &vpd->base;
> +       vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len);
> +       if (vpd->base.len == 0) {
> +               dev_dbg(&dev->dev, "Disabling VPD access.");
> +               dev->vpd = NULL;
> +               kfree(vpd);
> +               return -ENXIO;
> +       }
>         return 0;
>  }
>

You might want to incorporate the PCI_DEV_FLAGS_VPD_REF_F0 bits that
were added a while ago in order to avoid having to reset the length
each time.  You should only need to call this on function 0 for such a
device so you may want to consider adding a check for that into your
length function.  If that flag is set and we are not testing function
zero you could just default to 0 for the base length.
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux