diff mbox series

[RFC] cxl: switch usp to parent cxl_port

Message ID 20210924212817.489801-1-ben.widawsky@intel.com
State New, archived
Headers show
Series [RFC] cxl: switch usp to parent cxl_port | expand

Commit Message

Ben Widawsky Sept. 24, 2021, 9:28 p.m. UTC
Hi Dan.

As discussed in #cxl, I'm trying to do the switch enumeration and not entirely
sure of the best way to go about it (and no real way to test it). Do you have a
proposed way to do this better?


---
 drivers/cxl/core/bus.c | 65 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)


base-commit: 5c46722547fb375f460dbe293f40fad0ca32ab90
prerequisite-patch-id: f3e7ffbd5f60cd3f55ad8e174afd9d8c37d546f4
prerequisite-patch-id: a74a0ce5f4a1e4cec50e68181fcbb92624cb0687
prerequisite-patch-id: 3e2e86cbc2631b99c1b5c0179f35799d3df31f91
prerequisite-patch-id: 68cf009af310e296626b7842fc798fc778fcc98e
prerequisite-patch-id: 4063abbb095774d723ad727cb91bc8576d6a5532
prerequisite-patch-id: 3f11cc68d4dfc7f578f8c6a440663e9ef4e27466
prerequisite-patch-id: 5ef0b7ff2acf5896b955dd4f70a06581f42d7904
prerequisite-patch-id: 8cc6066d94bc859ff094c056d77eb63a967a3657
prerequisite-patch-id: b4fa2b34c36f3068743c965ebc01462ab016df4d
prerequisite-patch-id: a8798a409f1ff81979fb35a2c66a991ba6cc69f5
prerequisite-patch-id: d68e5ca2dbd408f206acb40c54f1ed9e6d981aa0

Comments

Dan Williams Sept. 27, 2021, 9:49 p.m. UTC | #1
On Fri, Sep 24, 2021 at 2:28 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
>
> Hi Dan.
>
> As discussed in #cxl, I'm trying to do the switch enumeration and not entirely
> sure of the best way to go about it (and no real way to test it). Do you have a
> proposed way to do this better?
>
>
> ---
>  drivers/cxl/core/bus.c | 65 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 65 insertions(+)
>
> diff --git a/drivers/cxl/core/bus.c b/drivers/cxl/core/bus.c
> index 936b6b5665c3..372d30b4bafd 100644
> --- a/drivers/cxl/core/bus.c
> +++ b/drivers/cxl/core/bus.c
> @@ -419,6 +419,71 @@ struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
>  }
>  EXPORT_SYMBOL_GPL(devm_cxl_add_port);
>
> +static int match_add_switches(struct pci_dev *pdev, void *data)
> +{
> +       struct cxl_walk_context *ctx = data;
> +       struct pci_bus *root_bus = ctx->root;
> +       struct device *dev = &pdev->dev;
> +       int type = pci_pcie_type(pdev);
> +       struct cxl_port *parent_port;
> +       struct cxl_register_map map;
> +       int rc;
> +
> +       if (pdev->bus != root_bus)
> +               return 0;

This bus is the peer-bus of the other root ports in the system. You
would want to be scanning the buses of the switch's subordinate
downstream ports. Also I don't think you want to filter by bus, and
just let pci_walk_bus() iterate the full sub-hierarchy.

So if pdev is a CXL uport, then the next uport would be on one of the
subordinate bridges. With the expectation that each cxl_port includes
a listing of its dports I would expect something like:

        list_for_each_entry(dport, &root_port->dports, list)
                pci_walk_bus(dport->subordinate, match_uport, ctx);

> +
> +       if (!pci_is_pcie(pdev))
> +               return 0;
> +       if (type != PCI_EXP_TYPE_UPSTREAM)
> +               return 0;

Yes, assuming that that pci_walk_bus() scans bridges before descending
to the next level you can try to scan for CXL capability on anything
that claims to be an upstream port. You could also save another
pci_walk_bus() by doing the dport check here too... i.e.

if (type == PCI_EXP_TYPE_DOWNSTREAM) {
    port = find_cxl_port(pdev->parent);
    ...determine id and component reg base...
    cxl_add_dport(port, pdev, ...);
}

> +
> +       /* Get component registers */
> +       rc = cxl_find_register_block(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
> +       if (rc) {
> +               ctx->error = rc;
> +       }
> +
> +       /* to_pci_dev(dev->parent) == downstream port, or root port */
> +       /* to_pci_dev(dev->parent->parent) == upstream port, or hostbridge */
> +
> +       if (dev_WARN_ONCE(dev, dev->parent->parent == NULL,
> +                         "No valid parent\n")) {
> +               ctx->error = -ENODEV;
> +               parent_port = NULL;

Not sure what this is for? I would think filtering pci_walk_bus() by
PCI_EXP_TYPE_{UP,DOWN}STREAM is sufficient and that this association
is assumed.

> +       } else {
> +               parent_port = to_cxl_port(dev->parent->parent);

No, you can't walk from a pci device to a cxl port, so it would need
something like find_cxl_port() like I handwaved above.

> +       }
> +
> +       devm_cxl_add_port(ctx->dev, dev,
> +                         pci_resource_start(pdev, map.barno) +
> +                                 map.block_offset,
> +                         parent_port);
> +
> +       return 0;
> +}
> +
> +int enumerate_ports_from_root_port(struct cxl_port *root_port)
> +{
> +       struct device *d = root_port->uport;
> +       struct cxl_walk_context ctx;
> +       struct pci_dev *pdev;
> +
> +       if (!dev_is_pci(d))
> +               return -ENODEV;
> +
> +       pdev = to_pci_dev(d);
> +       ctx = (struct cxl_walk_context){
> +               .dev = d,
> +               .root = pdev->bus,
> +               .port = root_port,
> +       };
> +
> +       pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL_GPL(enumerate_ports_from_root_port);
> +
>  static struct cxl_dport *find_dport(struct cxl_port *port, int id)
>  {
>         struct cxl_dport *dport;
>
> base-commit: 5c46722547fb375f460dbe293f40fad0ca32ab90
> prerequisite-patch-id: f3e7ffbd5f60cd3f55ad8e174afd9d8c37d546f4
> prerequisite-patch-id: a74a0ce5f4a1e4cec50e68181fcbb92624cb0687
> prerequisite-patch-id: 3e2e86cbc2631b99c1b5c0179f35799d3df31f91
> prerequisite-patch-id: 68cf009af310e296626b7842fc798fc778fcc98e
> prerequisite-patch-id: 4063abbb095774d723ad727cb91bc8576d6a5532
> prerequisite-patch-id: 3f11cc68d4dfc7f578f8c6a440663e9ef4e27466
> prerequisite-patch-id: 5ef0b7ff2acf5896b955dd4f70a06581f42d7904
> prerequisite-patch-id: 8cc6066d94bc859ff094c056d77eb63a967a3657
> prerequisite-patch-id: b4fa2b34c36f3068743c965ebc01462ab016df4d
> prerequisite-patch-id: a8798a409f1ff81979fb35a2c66a991ba6cc69f5
> prerequisite-patch-id: d68e5ca2dbd408f206acb40c54f1ed9e6d981aa0
> --
> 2.33.0
>
Ben Widawsky Sept. 27, 2021, 10:45 p.m. UTC | #2
On 21-09-27 14:49:40, Dan Williams wrote:
> On Fri, Sep 24, 2021 at 2:28 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> >
> > Hi Dan.
> >
> > As discussed in #cxl, I'm trying to do the switch enumeration and not entirely
> > sure of the best way to go about it (and no real way to test it). Do you have a
> > proposed way to do this better?
> >
> >
> > ---
> >  drivers/cxl/core/bus.c | 65 ++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 65 insertions(+)
> >
> > diff --git a/drivers/cxl/core/bus.c b/drivers/cxl/core/bus.c
> > index 936b6b5665c3..372d30b4bafd 100644
> > --- a/drivers/cxl/core/bus.c
> > +++ b/drivers/cxl/core/bus.c
> > @@ -419,6 +419,71 @@ struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
> >  }
> >  EXPORT_SYMBOL_GPL(devm_cxl_add_port);
> >
> > +static int match_add_switches(struct pci_dev *pdev, void *data)
> > +{
> > +       struct cxl_walk_context *ctx = data;
> > +       struct pci_bus *root_bus = ctx->root;
> > +       struct device *dev = &pdev->dev;
> > +       int type = pci_pcie_type(pdev);
> > +       struct cxl_port *parent_port;
> > +       struct cxl_register_map map;
> > +       int rc;
> > +
> > +       if (pdev->bus != root_bus)
> > +               return 0;
> 
> This bus is the peer-bus of the other root ports in the system.

I really dislike the word term "root port". You specifically mean other
hostbridges in the system, correct (or do you mean peer root ports in a
hostbridge)?

> You would want to be scanning the buses of the switch's subordinate downstream
> ports. Also I don't think you want to filter by bus, and just let
> pci_walk_bus() iterate the full sub-hierarchy.

Yes. That's fine. This was copy-paste from cxl_acpi, but I don't see a reason to
filter on the bus either.

> 
> So if pdev is a CXL uport, then the next uport would be on one of the
> subordinate bridges. With the expectation that each cxl_port includes
> a listing of its dports I would expect something like:
> 
>         list_for_each_entry(dport, &root_port->dports, list)
>                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> 

Not sure why I need to iterate over the dports, isn't just walking down the from
the root_port sufficient? I suppose it does some pre-filtering which might be a
win.


> > +
> > +       if (!pci_is_pcie(pdev))
> > +               return 0;
> > +       if (type != PCI_EXP_TYPE_UPSTREAM)
> > +               return 0;
> 
> Yes, assuming that that pci_walk_bus() scans bridges before descending
> to the next level you can try to scan for CXL capability on anything
> that claims to be an upstream port. You could also save another
> pci_walk_bus() by doing the dport check here too... i.e.
> 
> if (type == PCI_EXP_TYPE_DOWNSTREAM) {
>     port = find_cxl_port(pdev->parent);
>     ...determine id and component reg base...
>     cxl_add_dport(port, pdev, ...);
> }
> 

find_cxl_port handwave is where I was asking for advice. I believe I know how to
implement find_cxl_port(struct pci_dev *upstream_port), but...

(see bottom of email for sample with everything)


> > +
> > +       /* Get component registers */
> > +       rc = cxl_find_register_block(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
> > +       if (rc) {
> > +               ctx->error = rc;
> > +       }
> > +
> > +       /* to_pci_dev(dev->parent) == downstream port, or root port */
> > +       /* to_pci_dev(dev->parent->parent) == upstream port, or hostbridge */
> > +
> > +       if (dev_WARN_ONCE(dev, dev->parent->parent == NULL,
> > +                         "No valid parent\n")) {
> > +               ctx->error = -ENODEV;
> > +               parent_port = NULL;
> 
> Not sure what this is for? I would think filtering pci_walk_bus() by
> PCI_EXP_TYPE_{UP,DOWN}STREAM is sufficient and that this association
> is assumed.
> 
> > +       } else {
> > +               parent_port = to_cxl_port(dev->parent->parent);
> 
> No, you can't walk from a pci device to a cxl port, so it would need
> something like find_cxl_port() like I handwaved above.
> 

This is fundamentally where I'm "stuck". As I add switches, I need to get to the
parent port and I do not know how to do that. Is it safe to do
find_cxl_port(dev->parent->parent)?

> > +       }
> > +
> > +       devm_cxl_add_port(ctx->dev, dev,
> > +                         pci_resource_start(pdev, map.barno) +
> > +                                 map.block_offset,
> > +                         parent_port);
> > +
> > +       return 0;
> > +}
> > +
> > +int enumerate_ports_from_root_port(struct cxl_port *root_port)
> > +{
> > +       struct device *d = root_port->uport;
> > +       struct cxl_walk_context ctx;
> > +       struct pci_dev *pdev;
> > +
> > +       if (!dev_is_pci(d))
> > +               return -ENODEV;
> > +
> > +       pdev = to_pci_dev(d);
> > +       ctx = (struct cxl_walk_context){
> > +               .dev = d,
> > +               .root = pdev->bus,
> > +               .port = root_port,
> > +       };
> > +
> > +       pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> > +
> > +       return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(enumerate_ports_from_root_port);
> > +
> >  static struct cxl_dport *find_dport(struct cxl_port *port, int id)
> >  {
> >         struct cxl_dport *dport;
> >
> > base-commit: 5c46722547fb375f460dbe293f40fad0ca32ab90
> > prerequisite-patch-id: f3e7ffbd5f60cd3f55ad8e174afd9d8c37d546f4
> > prerequisite-patch-id: a74a0ce5f4a1e4cec50e68181fcbb92624cb0687
> > prerequisite-patch-id: 3e2e86cbc2631b99c1b5c0179f35799d3df31f91
> > prerequisite-patch-id: 68cf009af310e296626b7842fc798fc778fcc98e
> > prerequisite-patch-id: 4063abbb095774d723ad727cb91bc8576d6a5532
> > prerequisite-patch-id: 3f11cc68d4dfc7f578f8c6a440663e9ef4e27466
> > prerequisite-patch-id: 5ef0b7ff2acf5896b955dd4f70a06581f42d7904
> > prerequisite-patch-id: 8cc6066d94bc859ff094c056d77eb63a967a3657
> > prerequisite-patch-id: b4fa2b34c36f3068743c965ebc01462ab016df4d
> > prerequisite-patch-id: a8798a409f1ff81979fb35a2c66a991ba6cc69f5
> > prerequisite-patch-id: d68e5ca2dbd408f206acb40c54f1ed9e6d981aa0
> > --
> > 2.33.0
> >

int match_port(struct device *dev, const void *data)
{
        struct pci_dev *pdev = data;

        if (!is_cxl_port(dev))
                return 0;

        return to_cxl_port(dev)->uport == &pdev->dev;
}

struct cxl_port *find_cxl_port(struct pci_dev *upstream_port)
{
        struct device *port_dev;

        port_dev = bus_find_device(&cxl_bus_type, NULL, upstream_port, match_port);
        if (port_dev)
                return to_cxl_port(port_dev);

        return NULL;
}

static int match_add_switches(struct pci_dev *pdev, void *data)
{
        ...
	dev = &pdev->dev;

        if (type == PCI_EXP_TYPE_UPSTREAM) {
		parent_port = find_cxl_port(dev->parent->parent);
		if (!parent_port)
			return 1;

		port = devm_cxl_add_port(..., parent_port); //upstream port
                return 0;
	}

	if (type == PCI_EXP_TYPE_DOWNSTREAM) {
		uport = find_cxl_port(dev->parent);
                // get component reg base of dport
		cxl_add_dport(uport, dev, ...)
	}

        return 0;
}

{
... pci_walk_bus(pdev->bus, match_add_switches, &ctx);
}
Dan Williams Sept. 27, 2021, 11:54 p.m. UTC | #3
On Mon, Sep 27, 2021 at 3:45 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
>
> On 21-09-27 14:49:40, Dan Williams wrote:
> > On Fri, Sep 24, 2021 at 2:28 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> > >
> > > Hi Dan.
> > >
> > > As discussed in #cxl, I'm trying to do the switch enumeration and not entirely
> > > sure of the best way to go about it (and no real way to test it). Do you have a
> > > proposed way to do this better?
> > >
> > >
> > > ---
> > >  drivers/cxl/core/bus.c | 65 ++++++++++++++++++++++++++++++++++++++++++
> > >  1 file changed, 65 insertions(+)
> > >
> > > diff --git a/drivers/cxl/core/bus.c b/drivers/cxl/core/bus.c
> > > index 936b6b5665c3..372d30b4bafd 100644
> > > --- a/drivers/cxl/core/bus.c
> > > +++ b/drivers/cxl/core/bus.c
> > > @@ -419,6 +419,71 @@ struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
> > >  }
> > >  EXPORT_SYMBOL_GPL(devm_cxl_add_port);
> > >
> > > +static int match_add_switches(struct pci_dev *pdev, void *data)
> > > +{
> > > +       struct cxl_walk_context *ctx = data;
> > > +       struct pci_bus *root_bus = ctx->root;
> > > +       struct device *dev = &pdev->dev;
> > > +       int type = pci_pcie_type(pdev);
> > > +       struct cxl_port *parent_port;
> > > +       struct cxl_register_map map;
> > > +       int rc;
> > > +
> > > +       if (pdev->bus != root_bus)
> > > +               return 0;
> >
> > This bus is the peer-bus of the other root ports in the system.
>
> I really dislike the word term "root port". You specifically mean other
> hostbridges in the system, correct (or do you mean peer root ports in a
> hostbridge)?

I mean both.

All of the PCIE root ports in the system may appear to be on the same
root bus regardless of which hostbridge they belong, but that's not
relevant for switch scanning beneath root ports.

>
> > You would want to be scanning the buses of the switch's subordinate downstream
> > ports. Also I don't think you want to filter by bus, and just let
> > pci_walk_bus() iterate the full sub-hierarchy.
>
> Yes. That's fine. This was copy-paste from cxl_acpi, but I don't see a reason to
> filter on the bus either.

cxl_acpi is not scanning for subordinates like this code wants to do.

>
> >
> > So if pdev is a CXL uport, then the next uport would be on one of the
> > subordinate bridges. With the expectation that each cxl_port includes
> > a listing of its dports I would expect something like:
> >
> >         list_for_each_entry(dport, &root_port->dports, list)
> >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> >
>
> Not sure why I need to iterate over the dports, isn't just walking down the from
> the root_port sufficient? I suppose it does some pre-filtering which might be a
> win.

You know you can skip a level because that first level is handled by
cxl_acpi, and that first level has the ACPI hostbridge weirdness.

>
>
> > > +
> > > +       if (!pci_is_pcie(pdev))
> > > +               return 0;
> > > +       if (type != PCI_EXP_TYPE_UPSTREAM)
> > > +               return 0;
> >
> > Yes, assuming that that pci_walk_bus() scans bridges before descending
> > to the next level you can try to scan for CXL capability on anything
> > that claims to be an upstream port. You could also save another
> > pci_walk_bus() by doing the dport check here too... i.e.
> >
> > if (type == PCI_EXP_TYPE_DOWNSTREAM) {
> >     port = find_cxl_port(pdev->parent);
> >     ...determine id and component reg base...
> >     cxl_add_dport(port, pdev, ...);
> > }
> >
>
> find_cxl_port handwave is where I was asking for advice. I believe I know how to
> implement find_cxl_port(struct pci_dev *upstream_port), but...
>
> (see bottom of email for sample with everything)
>
>
> > > +
> > > +       /* Get component registers */
> > > +       rc = cxl_find_register_block(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
> > > +       if (rc) {
> > > +               ctx->error = rc;
> > > +       }
> > > +
> > > +       /* to_pci_dev(dev->parent) == downstream port, or root port */
> > > +       /* to_pci_dev(dev->parent->parent) == upstream port, or hostbridge */
> > > +
> > > +       if (dev_WARN_ONCE(dev, dev->parent->parent == NULL,
> > > +                         "No valid parent\n")) {
> > > +               ctx->error = -ENODEV;
> > > +               parent_port = NULL;
> >
> > Not sure what this is for? I would think filtering pci_walk_bus() by
> > PCI_EXP_TYPE_{UP,DOWN}STREAM is sufficient and that this association
> > is assumed.
> >
> > > +       } else {
> > > +               parent_port = to_cxl_port(dev->parent->parent);
> >
> > No, you can't walk from a pci device to a cxl port, so it would need
> > something like find_cxl_port() like I handwaved above.
> >
>
> This is fundamentally where I'm "stuck". As I add switches, I need to get to the
> parent port and I do not know how to do that. Is it safe to do
> find_cxl_port(dev->parent->parent)?

Yes, but only if you start the walk from dport->subordinate otherwise
this walks off the top of the hierarchy.

>
> > > +       }
> > > +
> > > +       devm_cxl_add_port(ctx->dev, dev,
> > > +                         pci_resource_start(pdev, map.barno) +
> > > +                                 map.block_offset,
> > > +                         parent_port);
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +int enumerate_ports_from_root_port(struct cxl_port *root_port)
> > > +{
> > > +       struct device *d = root_port->uport;
> > > +       struct cxl_walk_context ctx;
> > > +       struct pci_dev *pdev;
> > > +
> > > +       if (!dev_is_pci(d))
> > > +               return -ENODEV;
> > > +
> > > +       pdev = to_pci_dev(d);
> > > +       ctx = (struct cxl_walk_context){
> > > +               .dev = d,
> > > +               .root = pdev->bus,
> > > +               .port = root_port,
> > > +       };
> > > +
> > > +       pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> > > +
> > > +       return 0;
> > > +}
> > > +EXPORT_SYMBOL_GPL(enumerate_ports_from_root_port);
> > > +
> > >  static struct cxl_dport *find_dport(struct cxl_port *port, int id)
> > >  {
> > >         struct cxl_dport *dport;
> > >
> > > base-commit: 5c46722547fb375f460dbe293f40fad0ca32ab90
> > > prerequisite-patch-id: f3e7ffbd5f60cd3f55ad8e174afd9d8c37d546f4
> > > prerequisite-patch-id: a74a0ce5f4a1e4cec50e68181fcbb92624cb0687
> > > prerequisite-patch-id: 3e2e86cbc2631b99c1b5c0179f35799d3df31f91
> > > prerequisite-patch-id: 68cf009af310e296626b7842fc798fc778fcc98e
> > > prerequisite-patch-id: 4063abbb095774d723ad727cb91bc8576d6a5532
> > > prerequisite-patch-id: 3f11cc68d4dfc7f578f8c6a440663e9ef4e27466
> > > prerequisite-patch-id: 5ef0b7ff2acf5896b955dd4f70a06581f42d7904
> > > prerequisite-patch-id: 8cc6066d94bc859ff094c056d77eb63a967a3657
> > > prerequisite-patch-id: b4fa2b34c36f3068743c965ebc01462ab016df4d
> > > prerequisite-patch-id: a8798a409f1ff81979fb35a2c66a991ba6cc69f5
> > > prerequisite-patch-id: d68e5ca2dbd408f206acb40c54f1ed9e6d981aa0
> > > --
> > > 2.33.0
> > >
>
> int match_port(struct device *dev, const void *data)
> {
>         struct pci_dev *pdev = data;
>
>         if (!is_cxl_port(dev))
>                 return 0;
>
>         return to_cxl_port(dev)->uport == &pdev->dev;
> }
>
> struct cxl_port *find_cxl_port(struct pci_dev *upstream_port)
> {
>         struct device *port_dev;
>
>         port_dev = bus_find_device(&cxl_bus_type, NULL, upstream_port, match_port);
>         if (port_dev)
>                 return to_cxl_port(port_dev);
>
>         return NULL;
> }
>
> static int match_add_switches(struct pci_dev *pdev, void *data)
> {
>         ...
>         dev = &pdev->dev;
>
>         if (type == PCI_EXP_TYPE_UPSTREAM) {
>                 parent_port = find_cxl_port(dev->parent->parent);

Looks good.

For the final code you'll probably want to do find_cxl_port(dev) first
just to make sure you're not adding a port for a switch that was
scanned before in a previous API call.

>                 if (!parent_port)
>                         return 1;
>
>                 port = devm_cxl_add_port(..., parent_port); //upstream port

Looks good.

For the final code it will want a "put_device(&parent_port->dev)" here
to undo the reference taken by bus_find_device().

>                 return 0;
>         }
>
>         if (type == PCI_EXP_TYPE_DOWNSTREAM) {
>                 uport = find_cxl_port(dev->parent);
>                 // get component reg base of dport
>                 cxl_add_dport(uport, dev, ...)

Looks good.

>         }
>
>         return 0;
> }
>
> {
> ... pci_walk_bus(pdev->bus, match_add_switches, &ctx);

Per-above you probably want to start the scan below each root port, or
otherwise filter out the top-level scan work that cxl_acpi owns.
Ben Widawsky Sept. 28, 2021, 12:06 a.m. UTC | #4
On 21-09-27 16:54:25, Dan Williams wrote:
> On Mon, Sep 27, 2021 at 3:45 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> >
> > On 21-09-27 14:49:40, Dan Williams wrote:
> > > On Fri, Sep 24, 2021 at 2:28 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> > > >
> > > > Hi Dan.
> > > >
> > > > As discussed in #cxl, I'm trying to do the switch enumeration and not entirely
> > > > sure of the best way to go about it (and no real way to test it). Do you have a
> > > > proposed way to do this better?
> > > >
> > > >
> > > > ---
> > > >  drivers/cxl/core/bus.c | 65 ++++++++++++++++++++++++++++++++++++++++++
> > > >  1 file changed, 65 insertions(+)
> > > >
> > > > diff --git a/drivers/cxl/core/bus.c b/drivers/cxl/core/bus.c
> > > > index 936b6b5665c3..372d30b4bafd 100644
> > > > --- a/drivers/cxl/core/bus.c
> > > > +++ b/drivers/cxl/core/bus.c
> > > > @@ -419,6 +419,71 @@ struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
> > > >  }
> > > >  EXPORT_SYMBOL_GPL(devm_cxl_add_port);
> > > >
> > > > +static int match_add_switches(struct pci_dev *pdev, void *data)
> > > > +{
> > > > +       struct cxl_walk_context *ctx = data;
> > > > +       struct pci_bus *root_bus = ctx->root;
> > > > +       struct device *dev = &pdev->dev;
> > > > +       int type = pci_pcie_type(pdev);
> > > > +       struct cxl_port *parent_port;
> > > > +       struct cxl_register_map map;
> > > > +       int rc;
> > > > +
> > > > +       if (pdev->bus != root_bus)
> > > > +               return 0;
> > >
> > > This bus is the peer-bus of the other root ports in the system.
> >
> > I really dislike the word term "root port". You specifically mean other
> > hostbridges in the system, correct (or do you mean peer root ports in a
> > hostbridge)?
> 
> I mean both.
> 
> All of the PCIE root ports in the system may appear to be on the same
> root bus regardless of which hostbridge they belong, but that's not
> relevant for switch scanning beneath root ports.
> 
> >
> > > You would want to be scanning the buses of the switch's subordinate downstream
> > > ports. Also I don't think you want to filter by bus, and just let
> > > pci_walk_bus() iterate the full sub-hierarchy.
> >
> > Yes. That's fine. This was copy-paste from cxl_acpi, but I don't see a reason to
> > filter on the bus either.
> 
> cxl_acpi is not scanning for subordinates like this code wants to do.
> 
> >
> > >
> > > So if pdev is a CXL uport, then the next uport would be on one of the
> > > subordinate bridges. With the expectation that each cxl_port includes
> > > a listing of its dports I would expect something like:
> > >
> > >         list_for_each_entry(dport, &root_port->dports, list)
> > >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> > >
> >
> > Not sure why I need to iterate over the dports, isn't just walking down the from
> > the root_port sufficient? I suppose it does some pre-filtering which might be a
> > win.
> 
> You know you can skip a level because that first level is handled by
> cxl_acpi, and that first level has the ACPI hostbridge weirdness.
> 
> >
> >
> > > > +
> > > > +       if (!pci_is_pcie(pdev))
> > > > +               return 0;
> > > > +       if (type != PCI_EXP_TYPE_UPSTREAM)
> > > > +               return 0;
> > >
> > > Yes, assuming that that pci_walk_bus() scans bridges before descending
> > > to the next level you can try to scan for CXL capability on anything
> > > that claims to be an upstream port. You could also save another
> > > pci_walk_bus() by doing the dport check here too... i.e.
> > >
> > > if (type == PCI_EXP_TYPE_DOWNSTREAM) {
> > >     port = find_cxl_port(pdev->parent);
> > >     ...determine id and component reg base...
> > >     cxl_add_dport(port, pdev, ...);
> > > }
> > >
> >
> > find_cxl_port handwave is where I was asking for advice. I believe I know how to
> > implement find_cxl_port(struct pci_dev *upstream_port), but...
> >
> > (see bottom of email for sample with everything)
> >
> >
> > > > +
> > > > +       /* Get component registers */
> > > > +       rc = cxl_find_register_block(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
> > > > +       if (rc) {
> > > > +               ctx->error = rc;
> > > > +       }
> > > > +
> > > > +       /* to_pci_dev(dev->parent) == downstream port, or root port */
> > > > +       /* to_pci_dev(dev->parent->parent) == upstream port, or hostbridge */
> > > > +
> > > > +       if (dev_WARN_ONCE(dev, dev->parent->parent == NULL,
> > > > +                         "No valid parent\n")) {
> > > > +               ctx->error = -ENODEV;
> > > > +               parent_port = NULL;
> > >
> > > Not sure what this is for? I would think filtering pci_walk_bus() by
> > > PCI_EXP_TYPE_{UP,DOWN}STREAM is sufficient and that this association
> > > is assumed.
> > >
> > > > +       } else {
> > > > +               parent_port = to_cxl_port(dev->parent->parent);
> > >
> > > No, you can't walk from a pci device to a cxl port, so it would need
> > > something like find_cxl_port() like I handwaved above.
> > >
> >
> > This is fundamentally where I'm "stuck". As I add switches, I need to get to the
> > parent port and I do not know how to do that. Is it safe to do
> > find_cxl_port(dev->parent->parent)?
> 
> Yes, but only if you start the walk from dport->subordinate otherwise
> this walks off the top of the hierarchy.
> 
> >
> > > > +       }
> > > > +
> > > > +       devm_cxl_add_port(ctx->dev, dev,
> > > > +                         pci_resource_start(pdev, map.barno) +
> > > > +                                 map.block_offset,
> > > > +                         parent_port);
> > > > +
> > > > +       return 0;
> > > > +}
> > > > +
> > > > +int enumerate_ports_from_root_port(struct cxl_port *root_port)
> > > > +{
> > > > +       struct device *d = root_port->uport;
> > > > +       struct cxl_walk_context ctx;
> > > > +       struct pci_dev *pdev;
> > > > +
> > > > +       if (!dev_is_pci(d))
> > > > +               return -ENODEV;
> > > > +
> > > > +       pdev = to_pci_dev(d);
> > > > +       ctx = (struct cxl_walk_context){
> > > > +               .dev = d,
> > > > +               .root = pdev->bus,
> > > > +               .port = root_port,
> > > > +       };
> > > > +
> > > > +       pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> > > > +
> > > > +       return 0;
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(enumerate_ports_from_root_port);
> > > > +
> > > >  static struct cxl_dport *find_dport(struct cxl_port *port, int id)
> > > >  {
> > > >         struct cxl_dport *dport;
> > > >
> > > > base-commit: 5c46722547fb375f460dbe293f40fad0ca32ab90
> > > > prerequisite-patch-id: f3e7ffbd5f60cd3f55ad8e174afd9d8c37d546f4
> > > > prerequisite-patch-id: a74a0ce5f4a1e4cec50e68181fcbb92624cb0687
> > > > prerequisite-patch-id: 3e2e86cbc2631b99c1b5c0179f35799d3df31f91
> > > > prerequisite-patch-id: 68cf009af310e296626b7842fc798fc778fcc98e
> > > > prerequisite-patch-id: 4063abbb095774d723ad727cb91bc8576d6a5532
> > > > prerequisite-patch-id: 3f11cc68d4dfc7f578f8c6a440663e9ef4e27466
> > > > prerequisite-patch-id: 5ef0b7ff2acf5896b955dd4f70a06581f42d7904
> > > > prerequisite-patch-id: 8cc6066d94bc859ff094c056d77eb63a967a3657
> > > > prerequisite-patch-id: b4fa2b34c36f3068743c965ebc01462ab016df4d
> > > > prerequisite-patch-id: a8798a409f1ff81979fb35a2c66a991ba6cc69f5
> > > > prerequisite-patch-id: d68e5ca2dbd408f206acb40c54f1ed9e6d981aa0
> > > > --
> > > > 2.33.0
> > > >
> >
> > int match_port(struct device *dev, const void *data)
> > {
> >         struct pci_dev *pdev = data;
> >
> >         if (!is_cxl_port(dev))
> >                 return 0;
> >
> >         return to_cxl_port(dev)->uport == &pdev->dev;
> > }
> >
> > struct cxl_port *find_cxl_port(struct pci_dev *upstream_port)
> > {
> >         struct device *port_dev;
> >
> >         port_dev = bus_find_device(&cxl_bus_type, NULL, upstream_port, match_port);
> >         if (port_dev)
> >                 return to_cxl_port(port_dev);
> >
> >         return NULL;
> > }
> >
> > static int match_add_switches(struct pci_dev *pdev, void *data)
> > {
> >         ...
> >         dev = &pdev->dev;
> >
> >         if (type == PCI_EXP_TYPE_UPSTREAM) {
> >                 parent_port = find_cxl_port(dev->parent->parent);
> 
> Looks good.
> 
> For the final code you'll probably want to do find_cxl_port(dev) first
> just to make sure you're not adding a port for a switch that was
> scanned before in a previous API call.
> 
> >                 if (!parent_port)
> >                         return 1;
> >
> >                 port = devm_cxl_add_port(..., parent_port); //upstream port
> 
> Looks good.
> 
> For the final code it will want a "put_device(&parent_port->dev)" here
> to undo the reference taken by bus_find_device().
> 
> >                 return 0;
> >         }
> >
> >         if (type == PCI_EXP_TYPE_DOWNSTREAM) {
> >                 uport = find_cxl_port(dev->parent);
> >                 // get component reg base of dport
> >                 cxl_add_dport(uport, dev, ...)
> 
> Looks good.
> 
> >         }
> >
> >         return 0;
> > }
> >
> > {
> > ... pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> 
> Per-above you probably want to start the scan below each root port, or
> otherwise filter out the top-level scan work that cxl_acpi owns.

The scan will skip everything until it finds a USP or DSP which cxl_acpi
wouldn't be touching. I don't have any issue with:

>         list_for_each_entry(dport, &root_port->dports, list)
>                 pci_walk_bus(dport->subordinate, match_uport, ctx);

but I don't see any issue with the current code either. Did I miss something?
Dan Williams Sept. 28, 2021, 12:18 a.m. UTC | #5
On Mon, Sep 27, 2021 at 5:06 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
[..]
> > Per-above you probably want to start the scan below each root port, or
> > otherwise filter out the top-level scan work that cxl_acpi owns.
>
> The scan will skip everything until it finds a USP or DSP which cxl_acpi
> wouldn't be touching. I don't have any issue with:
>
> >         list_for_each_entry(dport, &root_port->dports, list)
> >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
>
> but I don't see any issue with the current code either. Did I miss something?

if (pdev->bus != root_bus)
    return 0;

Doesn't that always fail for the switches in question? Did I miss
where root_bus is updated?
Ben Widawsky Sept. 28, 2021, 12:21 a.m. UTC | #6
On 21-09-27 17:18:35, Dan Williams wrote:
> On Mon, Sep 27, 2021 at 5:06 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> [..]
> > > Per-above you probably want to start the scan below each root port, or
> > > otherwise filter out the top-level scan work that cxl_acpi owns.
> >
> > The scan will skip everything until it finds a USP or DSP which cxl_acpi
> > wouldn't be touching. I don't have any issue with:
> >
> > >         list_for_each_entry(dport, &root_port->dports, list)
> > >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> >
> > but I don't see any issue with the current code either. Did I miss something?
> 
> if (pdev->bus != root_bus)
>     return 0;
> 
> Doesn't that always fail for the switches in question? Did I miss
> where root_bus is updated?

In my previous comment I meant that I'd drop this check, but I see what you're
saying now. I need to pick some subordinate bus or I'll just walk over all
switches, even ones I don't care about...
Ben Widawsky Sept. 28, 2021, 12:32 a.m. UTC | #7
On 21-09-27 17:21:24, Ben Widawsky wrote:
> On 21-09-27 17:18:35, Dan Williams wrote:
> > On Mon, Sep 27, 2021 at 5:06 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> > [..]
> > > > Per-above you probably want to start the scan below each root port, or
> > > > otherwise filter out the top-level scan work that cxl_acpi owns.
> > >
> > > The scan will skip everything until it finds a USP or DSP which cxl_acpi
> > > wouldn't be touching. I don't have any issue with:
> > >
> > > >         list_for_each_entry(dport, &root_port->dports, list)
> > > >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> > >
> > > but I don't see any issue with the current code either. Did I miss something?
> > 
> > if (pdev->bus != root_bus)
> >     return 0;
> > 
> > Doesn't that always fail for the switches in question? Did I miss
> > where root_bus is updated?
> 
> In my previous comment I meant that I'd drop this check, but I see what you're
> saying now. I need to pick some subordinate bus or I'll just walk over all
> switches, even ones I don't care about...

Would this work?

-int enumerate_ports_from_root_port(struct cxl_port *root_port)
+int enumerate_ports_from_root_port(struct cxl_dport *root_port)
 {
-       struct device *d = root_port->uport;
+       struct device *d = root_port->dport;
        struct cxl_walk_context ctx;
        struct pci_dev *pdev;

        if (!dev_is_pci(d))
                return -ENODEV;

        pdev = to_pci_dev(d);
        ctx = (struct cxl_walk_context){
                .dev = d,
                .root = pdev->bus,
-               .port = root_port,
+               .port = root_port->port,
        };

        pci_walk_bus(pdev->bus, match_add_switches, &ctx);

        return 0;
 }
Dan Williams Sept. 28, 2021, 1:09 a.m. UTC | #8
On Mon, Sep 27, 2021 at 5:32 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
>
> On 21-09-27 17:21:24, Ben Widawsky wrote:
> > On 21-09-27 17:18:35, Dan Williams wrote:
> > > On Mon, Sep 27, 2021 at 5:06 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> > > [..]
> > > > > Per-above you probably want to start the scan below each root port, or
> > > > > otherwise filter out the top-level scan work that cxl_acpi owns.
> > > >
> > > > The scan will skip everything until it finds a USP or DSP which cxl_acpi
> > > > wouldn't be touching. I don't have any issue with:
> > > >
> > > > >         list_for_each_entry(dport, &root_port->dports, list)
> > > > >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> > > >
> > > > but I don't see any issue with the current code either. Did I miss something?
> > >
> > > if (pdev->bus != root_bus)
> > >     return 0;
> > >
> > > Doesn't that always fail for the switches in question? Did I miss
> > > where root_bus is updated?
> >
> > In my previous comment I meant that I'd drop this check, but I see what you're
> > saying now. I need to pick some subordinate bus or I'll just walk over all
> > switches, even ones I don't care about...
>
> Would this work?

Would need to see the whole patch in context, but I'm being thrown off
by the attempted reuse of 'struct cxl_walk_context'. That assumes that
the parent port is passed and only scans the top-level PCI bus.
Whereas for switches we're talking about walking all of them and using
something like find_cxl_port() to lookup parent ports as it goes.

>
> -int enumerate_ports_from_root_port(struct cxl_port *root_port)
> +int enumerate_ports_from_root_port(struct cxl_dport *root_port)
>  {
> -       struct device *d = root_port->uport;
> +       struct device *d = root_port->dport;
>         struct cxl_walk_context ctx;
>         struct pci_dev *pdev;
>
>         if (!dev_is_pci(d))
>                 return -ENODEV;
>
>         pdev = to_pci_dev(d);
>         ctx = (struct cxl_walk_context){
>                 .dev = d,
>                 .root = pdev->bus,
> -               .port = root_port,
> +               .port = root_port->port,

Per-above why would match_add_switches() care about this port once it
walks down a level in the tree?

>         };
>
>         pci_walk_bus(pdev->bus, match_add_switches, &ctx);
>
>         return 0;
>  }
>
>
Ben Widawsky Sept. 28, 2021, 3:23 a.m. UTC | #9
On 21-09-27 18:09:46, Dan Williams wrote:
> On Mon, Sep 27, 2021 at 5:32 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> >
> > On 21-09-27 17:21:24, Ben Widawsky wrote:
> > > On 21-09-27 17:18:35, Dan Williams wrote:
> > > > On Mon, Sep 27, 2021 at 5:06 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
> > > > [..]
> > > > > > Per-above you probably want to start the scan below each root port, or
> > > > > > otherwise filter out the top-level scan work that cxl_acpi owns.
> > > > >
> > > > > The scan will skip everything until it finds a USP or DSP which cxl_acpi
> > > > > wouldn't be touching. I don't have any issue with:
> > > > >
> > > > > >         list_for_each_entry(dport, &root_port->dports, list)
> > > > > >                 pci_walk_bus(dport->subordinate, match_uport, ctx);
> > > > >
> > > > > but I don't see any issue with the current code either. Did I miss something?
> > > >
> > > > if (pdev->bus != root_bus)
> > > >     return 0;
> > > >
> > > > Doesn't that always fail for the switches in question? Did I miss
> > > > where root_bus is updated?
> > >
> > > In my previous comment I meant that I'd drop this check, but I see what you're
> > > saying now. I need to pick some subordinate bus or I'll just walk over all
> > > switches, even ones I don't care about...
> >
> > Would this work?
> 
> Would need to see the whole patch in context, but I'm being thrown off
> by the attempted reuse of 'struct cxl_walk_context'. That assumes that
> the parent port is passed and only scans the top-level PCI bus.
> Whereas for switches we're talking about walking all of them and using
> something like find_cxl_port() to lookup parent ports as it goes.

cxl_walk_context is only used for convenience. It's not strictly required, but
it is a superset of what I was looking for.

> 
> >
> > -int enumerate_ports_from_root_port(struct cxl_port *root_port)
> > +int enumerate_ports_from_root_port(struct cxl_dport *root_port)
> >  {
> > -       struct device *d = root_port->uport;
> > +       struct device *d = root_port->dport;
> >         struct cxl_walk_context ctx;
> >         struct pci_dev *pdev;
> >
> >         if (!dev_is_pci(d))
> >                 return -ENODEV;
> >
> >         pdev = to_pci_dev(d);
> >         ctx = (struct cxl_walk_context){
> >                 .dev = d,
> >                 .root = pdev->bus,
> > -               .port = root_port,
> > +               .port = root_port->port,
> 
> Per-above why would match_add_switches() care about this port once it
> walks down a level in the tree?

I thought you wanted the host of every new port to be either the hostbridge or
the cfmws root port.

> 
> >         };
> >
> >         pci_walk_bus(pdev->bus, match_add_switches, &ctx);
> >
> >         return 0;
> >  }
> >
> >

I'll get more context out shortly.
Dan Williams Sept. 28, 2021, 4:29 a.m. UTC | #10
On Mon, Sep 27, 2021 at 8:23 PM Ben Widawsky <ben.widawsky@intel.com> wrote:
[..]
> > > -int enumerate_ports_from_root_port(struct cxl_port *root_port)
> > > +int enumerate_ports_from_root_port(struct cxl_dport *root_port)
> > >  {
> > > -       struct device *d = root_port->uport;
> > > +       struct device *d = root_port->dport;
> > >         struct cxl_walk_context ctx;
> > >         struct pci_dev *pdev;
> > >
> > >         if (!dev_is_pci(d))
> > >                 return -ENODEV;
> > >
> > >         pdev = to_pci_dev(d);
> > >         ctx = (struct cxl_walk_context){
> > >                 .dev = d,
> > >                 .root = pdev->bus,
> > > -               .port = root_port,
> > > +               .port = root_port->port,
> >
> > Per-above why would match_add_switches() care about this port once it
> > walks down a level in the tree?
>
> I thought you wanted the host of every new port to be either the hostbridge or
> the cfmws root port.

The host is the device that owns the devm context. I was thinking the
natural device for hosting the whole CXL port hierarchy is the device
that registered the root cxl_port, the ACPI0017 platform device. I
expect It would just be passed in directly to this 'enumerate' helper.
Although, it's more 'rescan' than 'enumerate'.
diff mbox series

Patch

diff --git a/drivers/cxl/core/bus.c b/drivers/cxl/core/bus.c
index 936b6b5665c3..372d30b4bafd 100644
--- a/drivers/cxl/core/bus.c
+++ b/drivers/cxl/core/bus.c
@@ -419,6 +419,71 @@  struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
 }
 EXPORT_SYMBOL_GPL(devm_cxl_add_port);
 
+static int match_add_switches(struct pci_dev *pdev, void *data)
+{
+	struct cxl_walk_context *ctx = data;
+	struct pci_bus *root_bus = ctx->root;
+	struct device *dev = &pdev->dev;
+	int type = pci_pcie_type(pdev);
+	struct cxl_port *parent_port;
+	struct cxl_register_map map;
+	int rc;
+
+	if (pdev->bus != root_bus)
+		return 0;
+
+	if (!pci_is_pcie(pdev))
+		return 0;
+	if (type != PCI_EXP_TYPE_UPSTREAM)
+		return 0;
+
+	/* Get component registers */
+	rc = cxl_find_register_block(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+	if (rc) {
+		ctx->error = rc;
+	}
+
+	/* to_pci_dev(dev->parent) == downstream port, or root port */
+	/* to_pci_dev(dev->parent->parent) == upstream port, or hostbridge */
+
+	if (dev_WARN_ONCE(dev, dev->parent->parent == NULL,
+			  "No valid parent\n")) {
+		ctx->error = -ENODEV;
+		parent_port = NULL;
+	} else {
+		parent_port = to_cxl_port(dev->parent->parent);
+	}
+
+	devm_cxl_add_port(ctx->dev, dev,
+			  pci_resource_start(pdev, map.barno) +
+				  map.block_offset,
+			  parent_port);
+
+	return 0;
+}
+
+int enumerate_ports_from_root_port(struct cxl_port *root_port)
+{
+	struct device *d = root_port->uport;
+	struct cxl_walk_context ctx;
+	struct pci_dev *pdev;
+
+	if (!dev_is_pci(d))
+		return -ENODEV;
+
+	pdev = to_pci_dev(d);
+	ctx = (struct cxl_walk_context){
+		.dev = d,
+		.root = pdev->bus,
+		.port = root_port,
+	};
+
+	pci_walk_bus(pdev->bus, match_add_switches, &ctx);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(enumerate_ports_from_root_port);
+
 static struct cxl_dport *find_dport(struct cxl_port *port, int id)
 {
 	struct cxl_dport *dport;