Mon, Jan 15, 2024 at 03:46:12PM +0100, Philipp Stanner kirjoitti:
> PCI's devres API is not extensible to ranged mappings and has
> bug-provoking features. Improve that by providing better alternatives.
> 
> When the original devres API for PCI was implemented, priority was given
> to the creation of a set of "pural functions" such as
> pcim_request_regions(). These functions have bit masks as parameters to
> specify which BARs shall get mapped. Most users, however, only use those
> to mapp 1-3 BARs.
> A complete set of "singular functions" does not exist.
> 
> As functions mapping / requesting multiple BARs at once have (almost) no
> mechanism in C to return the resources to the caller of the plural
> function, the devres API utilizes the iomap-table administrated by the
> function pcim_iomap_table().
> 
> The entire PCI devres implementation was strongly tied to that table
> which only allows for mapping whole, complete BARs, as the BAR's index
> is used as table index. Consequently, it's not possible to, e.g., have a
> pcim_iomap_range() function with that mechanism.
> 
> An additional problem is that pci-devres has been ipmlemented in a sort
> of "hybrid-mode": Some unmanaged functions have managed counterparts
> (e.g.: pci_iomap() <-> pcim_iomap()), making their managed nature
> obvious to the programmer. However, the region-request functions in
> pci.c, prefixed with pci_, behave either managed or unmanaged, depending
> on whether pci_enable_device() or pcim_enable_device() has been called
> in advance.
> 
> This hybrid API is confusing and should be more cleanly separated by
> providing always-managed functions prefixed with pcim_.
> 
> Thus, the existing devres API is not desirable because:
>       a) The vast majority of the users of the plural functions only
>          ever sets a single bit in the bit mask, consequently making
>          them singular functions anyways.
>       b) There is no mechanism to request / iomap only part of a BAR.
>       c) The iomap-table mechanism is over-engineered, complicated and
>          can by definition not perform bounds checks, thus, provoking
>          memory faults: pcim_iomap_table(pdev)[42]
>       d) region-request functions being sometimes managed and
>          sometimes not is bug-provoking.
> 
> Implement a set of singular pcim_ functions that use devres directly
> and bypass the legacy iomap table mechanism.
> Add devres.c to driver-api documentation.

...

> +struct pcim_addr_devres {
> +     enum pcim_addr_devres_type type;
> +     void __iomem *baseaddr;
> +     unsigned long offset;
> +     unsigned long len;
> +     short bar;
> +};
> +
> +static inline void pcim_addr_devres_clear(struct pcim_addr_devres *res)
> +{
> +     res->type = PCIM_ADDR_DEVRES_TYPE_INVALID;
> +     res->bar = -1;
> +     res->baseaddr = NULL;
> +     res->offset = 0;
> +     res->len = 0;

More robust (in case the data type gets extended) is memset() + individual
(non-0) sets.

> +}

...

> +static int __pcim_request_region_range(struct pci_dev *pdev, int bar,
> +             unsigned long offset, unsigned long maxlen,
> +             const char *name, int exclusive)
> +{
> +     resource_size_t start = pci_resource_start(pdev, bar);
> +     resource_size_t len = pci_resource_len(pdev, bar);
> +     unsigned long flags = pci_resource_flags(pdev, bar);
> +
> +     if (start == 0 || len == 0) /* that's an unused BAR. */
> +             return 0;
> +     if (len <= offset)
> +             return  -EINVAL;
> +
> +     start += offset;
> +     len -= offset;

> +     if (len > maxlen && maxlen != 0)
> +             len = maxlen;

        if (maxlen && ...)

?

> +     if (flags & IORESOURCE_IO) {
> +             if (!request_region(start, len, name))
> +                     return -EBUSY;
> +     } else if (flags & IORESOURCE_MEM) {
> +             if (!__request_mem_region(start, len, name, exclusive))
> +                     return -EBUSY;
> +     } else {
> +             /* That's not a device we can request anything on. */
> +             return -ENODEV;
> +     }

Hmm... Not sure, but the switch-case against type might be considered:

        switch (resource_type(...)) {
                ...
        }

> +     return 0;
> +}

> +static void __pcim_release_region_range(struct pci_dev *pdev, int bar,
> +             unsigned long offset, unsigned long maxlen)
> +{
> +     resource_size_t start = pci_resource_start(pdev, bar);
> +     resource_size_t len = pci_resource_len(pdev, bar);
> +     unsigned long flags = pci_resource_flags(pdev, bar);
> +
> +     if (len <= offset || start == 0)
> +             return;
> +
> +     if (len == 0 || maxlen == 0) /* This an unused BAR. Do nothing. */
> +             return;
> +
> +     start += offset;
> +     len -= offset;
> +
> +     if (len > maxlen)
> +             len = maxlen;

This part is quite a duplication of the above function, no?

> +     if (flags & IORESOURCE_IO)
> +             release_region(start, len);
> +     else if (flags & IORESOURCE_MEM)
> +             release_mem_region(start, len);
> +}

...

> +static int __pcim_request_region(struct pci_dev *pdev, int bar,
> +             const char *name, int exclusive)
> +{
> +     const unsigned long offset = 0;
> +     const unsigned long len = pci_resource_len(pdev, bar);

How const anyhow useful here?
Ditto for other places like this.

> +     return __pcim_request_region_range(pdev, bar, offset, len, name, 
> exclusive);
> +}

...

> +static int pcim_addr_resources_match(struct device *dev, void *a_raw, void 
> *b_raw)
> +{
> +     struct pcim_addr_devres *a, *b;
> +
> +     a = a_raw;
> +     b = b_raw;

> +     (void)dev; /* unused. */

Why do we need this?

> +     if (a->type != b->type)
> +             return 0;
> +
> +     switch (a->type) {
> +     case PCIM_ADDR_DEVRES_TYPE_REGION:
> +     case PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING:
> +             return a->bar == b->bar;
> +     case PCIM_ADDR_DEVRES_TYPE_MAPPING:
> +             return a->baseaddr == b->baseaddr;
> +     case PCIM_ADDR_DEVRES_TYPE_REGION_RANGE_MAPPING:
> +             return a->bar == b->bar &&
> +                     a->offset == b->offset && a->len == b->len;

Indentation or made it a single line.

> +     default:
> +             break;
> +     }
> +
> +     return 0;

return directly from default case.

> +}

...

> +/**
> + * pcim_iomap_region - Request and iomap a PCI BAR
> + * @pdev: PCI device to map IO resources for
> + * @bar: Index of a BAR to map
> + * @name: Name associated with the request
> + *
> + * Returns __iomem pointer on success, an IOMEM_ERR_PTR on failure.

Please, make sure the kernel-doc won't complain

        scripts/kernel-doc -v -none -Wall ...

> + * Mapping and region will get automatically released on driver detach. If
> + * desired, release manually only with pcim_iounmap_region().
> + */
> +void __iomem *pcim_iomap_region(struct pci_dev *pdev, int bar, const char 
> *name)
> +{
> +     int ret = 0;

Redundant assignment.

> +     struct pcim_addr_devres *res;

Perhaps reversed xmas tree order?

> +     res = pcim_addr_devres_alloc(pdev);
> +     if (!res)
> +             return IOMEM_ERR_PTR(-ENOMEM);
> +
> +     res->type = PCIM_ADDR_DEVRES_TYPE_REGION_MAPPING;
> +     res->bar = bar;
> +
> +     ret = __pcim_request_region(pdev, bar, name, 0);
> +     if (ret != 0)
> +             goto err_region;
> +
> +     res->baseaddr = pci_iomap(pdev, bar, 0);
> +     if (!res->baseaddr) {
> +             ret = -EINVAL;
> +             goto err_iomap;
> +     }
> +
> +     devres_add(&pdev->dev, res);
> +     return res->baseaddr;
> +
> +err_iomap:
> +     __pcim_release_region(pdev, bar);
> +err_region:
> +     pcim_addr_devres_free(res);
> +
> +     return IOMEM_ERR_PTR(ret);
> +}

...

> +static int _pcim_request_region(struct pci_dev *pdev, int bar, const char 
> *name,
> +             int request_flags)

Indentation?

> +{
> +     int ret = 0;

Unneded assignment. Also fix this in other places.

> +     struct pcim_addr_devres *res;
> +
> +     res = pcim_addr_devres_alloc(pdev);
> +     if (!res)
> +             return -ENOMEM;
> +     res->type = PCIM_ADDR_DEVRES_TYPE_REGION;
> +     res->bar = bar;
> +
> +     ret = __pcim_request_region(pdev, bar, name, request_flags);
> +     if (ret != 0) {

        if (ret)

Also fix this in other places.

> +             pcim_addr_devres_free(res);
> +             return ret;
> +     }
> +
> +     devres_add(&pdev->dev, res);
> +     return 0;
> +}

-- 
With Best Regards,
Andy Shevchenko


Reply via email to