diff options
| author | Dmitry Torokhov <[email protected]> | 2023-05-01 15:20:08 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-05-01 15:20:08 -0700 | 
| commit | 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e (patch) | |
| tree | d57f3a63479a07b4e0cece029886e76e04feb984 /drivers/pci/setup-bus.c | |
| parent | 5dc63e56a9cf8df0b59c234a505a1653f1bdf885 (diff) | |
| parent | 53bea86b5712c7491bb3dae12e271666df0a308c (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.4 merge window.
Diffstat (limited to 'drivers/pci/setup-bus.c')
| -rw-r--r-- | drivers/pci/setup-bus.c | 236 | 
1 files changed, 170 insertions, 66 deletions
diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index b4096598dbcb..c690572b10ce 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1765,12 +1765,70 @@ static void adjust_bridge_window(struct pci_dev *bridge, struct resource *res,  		add_size = size - new_size;  		pci_dbg(bridge, "bridge window %pR shrunken by %pa\n", res,  			&add_size); +	} else { +		return;  	}  	res->end = res->start + new_size - 1; -	remove_from_list(add_list, res); + +	/* If the resource is part of the add_list, remove it now */ +	if (add_list) +		remove_from_list(add_list, res); +} + +static void remove_dev_resource(struct resource *avail, struct pci_dev *dev, +				struct resource *res) +{ +	resource_size_t size, align, tmp; + +	size = resource_size(res); +	if (!size) +		return; + +	align = pci_resource_alignment(dev, res); +	align = align ? ALIGN(avail->start, align) - avail->start : 0; +	tmp = align + size; +	avail->start = min(avail->start + tmp, avail->end + 1); +} + +static void remove_dev_resources(struct pci_dev *dev, struct resource *io, +				 struct resource *mmio, +				 struct resource *mmio_pref) +{ +	int i; + +	for (i = 0; i < PCI_NUM_RESOURCES; i++) { +		struct resource *res = &dev->resource[i]; + +		if (resource_type(res) == IORESOURCE_IO) { +			remove_dev_resource(io, dev, res); +		} else if (resource_type(res) == IORESOURCE_MEM) { + +			/* +			 * Make sure prefetchable memory is reduced from +			 * the correct resource. Specifically we put 32-bit +			 * prefetchable memory in non-prefetchable window +			 * if there is an 64-bit pretchable window. +			 * +			 * See comments in __pci_bus_size_bridges() for +			 * more information. +			 */ +			if ((res->flags & IORESOURCE_PREFETCH) && +			    ((res->flags & IORESOURCE_MEM_64) == +			     (mmio_pref->flags & IORESOURCE_MEM_64))) +				remove_dev_resource(mmio_pref, dev, res); +			else +				remove_dev_resource(mmio, dev, res); +		} +	}  } +/* + * io, mmio and mmio_pref contain the total amount of bridge window space + * available. This includes the minimal space needed to cover all the + * existing devices on the bus and the possible extra space that can be + * shared with the bridges. + */  static void pci_bus_distribute_available_resources(struct pci_bus *bus,  					    struct list_head *add_list,  					    struct resource io, @@ -1780,7 +1838,7 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus,  	unsigned int normal_bridges = 0, hotplug_bridges = 0;  	struct resource *io_res, *mmio_res, *mmio_pref_res;  	struct pci_dev *dev, *bridge = bus->self; -	resource_size_t io_per_hp, mmio_per_hp, mmio_pref_per_hp, align; +	resource_size_t io_per_b, mmio_per_b, mmio_pref_per_b, align;  	io_res = &bridge->resource[PCI_BRIDGE_IO_WINDOW];  	mmio_res = &bridge->resource[PCI_BRIDGE_MEM_WINDOW]; @@ -1824,94 +1882,88 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus,  			normal_bridges++;  	} +	if (!(hotplug_bridges + normal_bridges)) +		return; +  	/* -	 * There is only one bridge on the bus so it gets all available -	 * resources which it can then distribute to the possible hotplug -	 * bridges below. +	 * Calculate the amount of space we can forward from "bus" to any +	 * downstream buses, i.e., the space left over after assigning the +	 * BARs and windows on "bus".  	 */ -	if (hotplug_bridges + normal_bridges == 1) { -		dev = list_first_entry(&bus->devices, struct pci_dev, bus_list); -		if (dev->subordinate) -			pci_bus_distribute_available_resources(dev->subordinate, -				add_list, io, mmio, mmio_pref); -		return; +	list_for_each_entry(dev, &bus->devices, bus_list) { +		if (!dev->is_virtfn) +			remove_dev_resources(dev, &io, &mmio, &mmio_pref);  	} -	if (hotplug_bridges == 0) -		return; -  	/* -	 * Calculate the total amount of extra resource space we can -	 * pass to bridges below this one.  This is basically the -	 * extra space reduced by the minimal required space for the -	 * non-hotplug bridges. +	 * If there is at least one hotplug bridge on this bus it gets all +	 * the extra resource space that was left after the reductions +	 * above. +	 * +	 * If there are no hotplug bridges the extra resource space is +	 * split between non-hotplug bridges. This is to allow possible +	 * hotplug bridges below them to get the extra space as well.  	 */ +	if (hotplug_bridges) { +		io_per_b = div64_ul(resource_size(&io), hotplug_bridges); +		mmio_per_b = div64_ul(resource_size(&mmio), hotplug_bridges); +		mmio_pref_per_b = div64_ul(resource_size(&mmio_pref), +					   hotplug_bridges); +	} else { +		io_per_b = div64_ul(resource_size(&io), normal_bridges); +		mmio_per_b = div64_ul(resource_size(&mmio), normal_bridges); +		mmio_pref_per_b = div64_ul(resource_size(&mmio_pref), +					   normal_bridges); +	} +  	for_each_pci_bridge(dev, bus) { -		resource_size_t used_size;  		struct resource *res; +		struct pci_bus *b; -		if (dev->is_hotplug_bridge) +		b = dev->subordinate; +		if (!b) +			continue; +		if (hotplug_bridges && !dev->is_hotplug_bridge)  			continue; +		res = &dev->resource[PCI_BRIDGE_IO_WINDOW]; +  		/* -		 * Reduce the available resource space by what the -		 * bridge and devices below it occupy. +		 * Make sure the split resource space is properly aligned +		 * for bridge windows (align it down to avoid going above +		 * what is available).  		 */ -		res = &dev->resource[PCI_BRIDGE_IO_WINDOW];  		align = pci_resource_alignment(dev, res); -		align = align ? ALIGN(io.start, align) - io.start : 0; -		used_size = align + resource_size(res); -		if (!res->parent) -			io.start = min(io.start + used_size, io.end + 1); +		io.end = align ? io.start + ALIGN_DOWN(io_per_b, align) - 1 +			       : io.start + io_per_b - 1; + +		/* +		 * The x_per_b holds the extra resource space that can be +		 * added for each bridge but there is the minimal already +		 * reserved as well so adjust x.start down accordingly to +		 * cover the whole space. +		 */ +		io.start -= resource_size(res);  		res = &dev->resource[PCI_BRIDGE_MEM_WINDOW];  		align = pci_resource_alignment(dev, res); -		align = align ? ALIGN(mmio.start, align) - mmio.start : 0; -		used_size = align + resource_size(res); -		if (!res->parent) -			mmio.start = min(mmio.start + used_size, mmio.end + 1); +		mmio.end = align ? mmio.start + ALIGN_DOWN(mmio_per_b, align) - 1 +				 : mmio.start + mmio_per_b - 1; +		mmio.start -= resource_size(res);  		res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW];  		align = pci_resource_alignment(dev, res); -		align = align ? ALIGN(mmio_pref.start, align) - -			mmio_pref.start : 0; -		used_size = align + resource_size(res); -		if (!res->parent) -			mmio_pref.start = min(mmio_pref.start + used_size, -				mmio_pref.end + 1); -	} - -	io_per_hp = div64_ul(resource_size(&io), hotplug_bridges); -	mmio_per_hp = div64_ul(resource_size(&mmio), hotplug_bridges); -	mmio_pref_per_hp = div64_ul(resource_size(&mmio_pref), -		hotplug_bridges); - -	/* -	 * Go over devices on this bus and distribute the remaining -	 * resource space between hotplug bridges. -	 */ -	for_each_pci_bridge(dev, bus) { -		struct pci_bus *b; - -		b = dev->subordinate; -		if (!b || !dev->is_hotplug_bridge) -			continue; - -		/* -		 * Distribute available extra resources equally between -		 * hotplug-capable downstream ports taking alignment into -		 * account. -		 */ -		io.end = io.start + io_per_hp - 1; -		mmio.end = mmio.start + mmio_per_hp - 1; -		mmio_pref.end = mmio_pref.start + mmio_pref_per_hp - 1; +		mmio_pref.end = align ? mmio_pref.start + +					ALIGN_DOWN(mmio_pref_per_b, align) - 1 +				      : mmio_pref.start + mmio_pref_per_b - 1; +		mmio_pref.start -= resource_size(res);  		pci_bus_distribute_available_resources(b, add_list, io, mmio,  						       mmio_pref); -		io.start += io_per_hp; -		mmio.start += mmio_per_hp; -		mmio_pref.start += mmio_pref_per_hp; +		io.start += io.end + 1; +		mmio.start += mmio.end + 1; +		mmio_pref.start += mmio_pref.end + 1;  	}  } @@ -1923,6 +1975,8 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge,  	if (!bridge->is_hotplug_bridge)  		return; +	pci_dbg(bridge, "distributing available resources\n"); +  	/* Take the initial extra resources from the hotplug port */  	available_io = bridge->resource[PCI_BRIDGE_IO_WINDOW];  	available_mmio = bridge->resource[PCI_BRIDGE_MEM_WINDOW]; @@ -1934,6 +1988,54 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge,  					       available_mmio_pref);  } +static bool pci_bridge_resources_not_assigned(struct pci_dev *dev) +{ +	const struct resource *r; + +	/* +	 * If the child device's resources are not yet assigned it means we +	 * are configuring them (not the boot firmware), so we should be +	 * able to extend the upstream bridge resources in the same way we +	 * do with the normal hotplug case. +	 */ +	r = &dev->resource[PCI_BRIDGE_IO_WINDOW]; +	if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) +		return false; +	r = &dev->resource[PCI_BRIDGE_MEM_WINDOW]; +	if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) +		return false; +	r = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW]; +	if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) +		return false; + +	return true; +} + +static void +pci_root_bus_distribute_available_resources(struct pci_bus *bus, +					    struct list_head *add_list) +{ +	struct pci_dev *dev, *bridge = bus->self; + +	for_each_pci_bridge(dev, bus) { +		struct pci_bus *b; + +		b = dev->subordinate; +		if (!b) +			continue; + +		/* +		 * Need to check "bridge" here too because it is NULL +		 * in case of root bus. +		 */ +		if (bridge && pci_bridge_resources_not_assigned(dev)) +			pci_bridge_distribute_available_resources(bridge, +								  add_list); +		else +			pci_root_bus_distribute_available_resources(b, add_list); +	} +} +  /*   * First try will not touch PCI bridge res.   * Second and later try will clear small leaf bridge res. @@ -1973,6 +2075,8 @@ again:  	 */  	__pci_bus_size_bridges(bus, add_list); +	pci_root_bus_distribute_available_resources(bus, add_list); +  	/* Depth last, allocate resources and update the hardware. */  	__pci_bus_assign_resources(bus, add_list, &fail_head);  	if (add_list)  |