mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	cxl for 6.0
- Introduce a 'struct cxl_region' object with support for provisioning
   and assembling persistent memory regions.
 
 - Introduce alloc_free_mem_region() to accompany the existing
   request_free_mem_region() as a method to allocate physical memory
   capacity out of an existing resource.
 
 - Export insert_resource_expand_to_fit() for the CXL subsystem to
   late-publish CXL platform windows in iomem_resource.
 
 - Add a polled mode PCI DOE (Data Object Exchange) driver service and
   use it in cxl_pci to retrieve the CDAT (Coherent Device Attribute
   Table).
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQSbo+XnGs+rwLz9XGXfioYZHlFsZwUCYvLYmAAKCRDfioYZHlFs
 Z0pbAQC/3j+WriWpU7CdhrnZI1Wqn+x5IIklF0Lc4/f6LwGZtAEAsSbLpItzvwqx
 M/rcLaeLpwYlgvS1JjdsuQ2VQ7KOtAs=
 =ehNT
 -----END PGP SIGNATURE-----
Merge tag 'cxl-for-6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl
Pull cxl updates from Dan Williams:
 "Compute Express Link (CXL) updates for 6.0:
   - Introduce a 'struct cxl_region' object with support for
     provisioning and assembling persistent memory regions.
   - Introduce alloc_free_mem_region() to accompany the existing
     request_free_mem_region() as a method to allocate physical memory
     capacity out of an existing resource.
   - Export insert_resource_expand_to_fit() for the CXL subsystem to
     late-publish CXL platform windows in iomem_resource.
   - Add a polled mode PCI DOE (Data Object Exchange) driver service and
     use it in cxl_pci to retrieve the CDAT (Coherent Device Attribute
     Table)"
* tag 'cxl-for-6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl: (74 commits)
  cxl/hdm: Fix skip allocations vs multiple pmem allocations
  cxl/region: Disallow region granularity != window granularity
  cxl/region: Fix x1 interleave to greater than x1 interleave routing
  cxl/region: Move HPA setup to cxl_region_attach()
  cxl/region: Fix decoder interleave programming
  Documentation: cxl: remove dangling kernel-doc reference
  cxl/region: describe targets and nr_targets members of cxl_region_params
  cxl/regions: add padding for cxl_rr_ep_add nested lists
  cxl/region: Fix IS_ERR() vs NULL check
  cxl/region: Fix region reference target accounting
  cxl/region: Fix region commit uninitialized variable warning
  cxl/region: Fix port setup uninitialized variable warnings
  cxl/region: Stop initializing interleave granularity
  cxl/hdm: Fix DPA reservation vs cxl_endpoint_decoder lifetime
  cxl/acpi: Minimize granularity for x1 interleaves
  cxl/region: Delete 'region' attribute from root decoders
  cxl/acpi: Autoload driver for 'cxl_acpi' test devices
  cxl/region: decrement ->nr_targets on error in cxl_region_attach()
  cxl/region: prevent underflow in ways_to_cxl()
  cxl/region: uninitialized variable in alloc_hpa()
  ...
			
			
This commit is contained in:
		
						commit
						c235698355
					
				
					 39 changed files with 5508 additions and 586 deletions
				
			
		| 
						 | 
					@ -516,6 +516,7 @@ ForEachMacros:
 | 
				
			||||||
  - 'of_property_for_each_string'
 | 
					  - 'of_property_for_each_string'
 | 
				
			||||||
  - 'of_property_for_each_u32'
 | 
					  - 'of_property_for_each_u32'
 | 
				
			||||||
  - 'pci_bus_for_each_resource'
 | 
					  - 'pci_bus_for_each_resource'
 | 
				
			||||||
 | 
					  - 'pci_doe_for_each_off'
 | 
				
			||||||
  - 'pcl_for_each_chunk'
 | 
					  - 'pcl_for_each_chunk'
 | 
				
			||||||
  - 'pcl_for_each_segment'
 | 
					  - 'pcl_for_each_segment'
 | 
				
			||||||
  - 'pcm_for_each_format'
 | 
					  - 'pcm_for_each_format'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ Description:
 | 
				
			||||||
		all descendant memdevs for unbind. Writing '1' to this attribute
 | 
							all descendant memdevs for unbind. Writing '1' to this attribute
 | 
				
			||||||
		flushes that work.
 | 
							flushes that work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/memX/firmware_version
 | 
					What:		/sys/bus/cxl/devices/memX/firmware_version
 | 
				
			||||||
Date:		December, 2020
 | 
					Date:		December, 2020
 | 
				
			||||||
KernelVersion:	v5.12
 | 
					KernelVersion:	v5.12
 | 
				
			||||||
| 
						 | 
					@ -16,6 +17,7 @@ Description:
 | 
				
			||||||
		Memory Device Output Payload in the CXL-2.0
 | 
							Memory Device Output Payload in the CXL-2.0
 | 
				
			||||||
		specification.
 | 
							specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/memX/ram/size
 | 
					What:		/sys/bus/cxl/devices/memX/ram/size
 | 
				
			||||||
Date:		December, 2020
 | 
					Date:		December, 2020
 | 
				
			||||||
KernelVersion:	v5.12
 | 
					KernelVersion:	v5.12
 | 
				
			||||||
| 
						 | 
					@ -25,6 +27,7 @@ Description:
 | 
				
			||||||
		identically named field in the Identify Memory Device Output
 | 
							identically named field in the Identify Memory Device Output
 | 
				
			||||||
		Payload in the CXL-2.0 specification.
 | 
							Payload in the CXL-2.0 specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/memX/pmem/size
 | 
					What:		/sys/bus/cxl/devices/memX/pmem/size
 | 
				
			||||||
Date:		December, 2020
 | 
					Date:		December, 2020
 | 
				
			||||||
KernelVersion:	v5.12
 | 
					KernelVersion:	v5.12
 | 
				
			||||||
| 
						 | 
					@ -34,6 +37,7 @@ Description:
 | 
				
			||||||
		identically named field in the Identify Memory Device Output
 | 
							identically named field in the Identify Memory Device Output
 | 
				
			||||||
		Payload in the CXL-2.0 specification.
 | 
							Payload in the CXL-2.0 specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/memX/serial
 | 
					What:		/sys/bus/cxl/devices/memX/serial
 | 
				
			||||||
Date:		January, 2022
 | 
					Date:		January, 2022
 | 
				
			||||||
KernelVersion:	v5.18
 | 
					KernelVersion:	v5.18
 | 
				
			||||||
| 
						 | 
					@ -43,6 +47,7 @@ Description:
 | 
				
			||||||
		capability. Mandatory for CXL devices, see CXL 2.0 8.1.12.2
 | 
							capability. Mandatory for CXL devices, see CXL 2.0 8.1.12.2
 | 
				
			||||||
		Memory Device PCIe Capabilities and Extended Capabilities.
 | 
							Memory Device PCIe Capabilities and Extended Capabilities.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/memX/numa_node
 | 
					What:		/sys/bus/cxl/devices/memX/numa_node
 | 
				
			||||||
Date:		January, 2022
 | 
					Date:		January, 2022
 | 
				
			||||||
KernelVersion:	v5.18
 | 
					KernelVersion:	v5.18
 | 
				
			||||||
| 
						 | 
					@ -52,114 +57,334 @@ Description:
 | 
				
			||||||
		host PCI device for this memory device, emit the CPU node
 | 
							host PCI device for this memory device, emit the CPU node
 | 
				
			||||||
		affinity for this device.
 | 
							affinity for this device.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/*/devtype
 | 
					What:		/sys/bus/cxl/devices/*/devtype
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL device objects export the devtype attribute which mirrors
 | 
							(RO) CXL device objects export the devtype attribute which
 | 
				
			||||||
		the same value communicated in the DEVTYPE environment variable
 | 
							mirrors the same value communicated in the DEVTYPE environment
 | 
				
			||||||
		for uevents for devices on the "cxl" bus.
 | 
							variable for uevents for devices on the "cxl" bus.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/*/modalias
 | 
					What:		/sys/bus/cxl/devices/*/modalias
 | 
				
			||||||
Date:		December, 2021
 | 
					Date:		December, 2021
 | 
				
			||||||
KernelVersion:	v5.18
 | 
					KernelVersion:	v5.18
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL device objects export the modalias attribute which mirrors
 | 
							(RO) CXL device objects export the modalias attribute which
 | 
				
			||||||
		the same value communicated in the MODALIAS environment variable
 | 
							mirrors the same value communicated in the MODALIAS environment
 | 
				
			||||||
		for uevents for devices on the "cxl" bus.
 | 
							variable for uevents for devices on the "cxl" bus.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/portX/uport
 | 
					What:		/sys/bus/cxl/devices/portX/uport
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL port objects are enumerated from either a platform firmware
 | 
							(RO) CXL port objects are enumerated from either a platform
 | 
				
			||||||
		device (ACPI0017 and ACPI0016) or PCIe switch upstream port with
 | 
							firmware device (ACPI0017 and ACPI0016) or PCIe switch upstream
 | 
				
			||||||
		CXL component registers. The 'uport' symlink connects the CXL
 | 
							port with CXL component registers. The 'uport' symlink connects
 | 
				
			||||||
		portX object to the device that published the CXL port
 | 
							the CXL portX object to the device that published the CXL port
 | 
				
			||||||
		capability.
 | 
							capability.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/portX/dportY
 | 
					What:		/sys/bus/cxl/devices/portX/dportY
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL port objects are enumerated from either a platform firmware
 | 
							(RO) CXL port objects are enumerated from either a platform
 | 
				
			||||||
		device (ACPI0017 and ACPI0016) or PCIe switch upstream port with
 | 
							firmware device (ACPI0017 and ACPI0016) or PCIe switch upstream
 | 
				
			||||||
		CXL component registers. The 'dportY' symlink identifies one or
 | 
							port with CXL component registers. The 'dportY' symlink
 | 
				
			||||||
		more downstream ports that the upstream port may target in its
 | 
							identifies one or more downstream ports that the upstream port
 | 
				
			||||||
		decode of CXL memory resources.  The 'Y' integer reflects the
 | 
							may target in its decode of CXL memory resources.  The 'Y'
 | 
				
			||||||
		hardware port unique-id used in the hardware decoder target
 | 
							integer reflects the hardware port unique-id used in the
 | 
				
			||||||
		list.
 | 
							hardware decoder target list.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL decoder objects are enumerated from either a platform
 | 
							(RO) CXL decoder objects are enumerated from either a platform
 | 
				
			||||||
		firmware description, or a CXL HDM decoder register set in a
 | 
							firmware description, or a CXL HDM decoder register set in a
 | 
				
			||||||
		PCIe device (see CXL 2.0 section 8.2.5.12 CXL HDM Decoder
 | 
							PCIe device (see CXL 2.0 section 8.2.5.12 CXL HDM Decoder
 | 
				
			||||||
		Capability Structure). The 'X' in decoderX.Y represents the
 | 
							Capability Structure). The 'X' in decoderX.Y represents the
 | 
				
			||||||
		cxl_port container of this decoder, and 'Y' represents the
 | 
							cxl_port container of this decoder, and 'Y' represents the
 | 
				
			||||||
		instance id of a given decoder resource.
 | 
							instance id of a given decoder resource.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y/{start,size}
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/{start,size}
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		The 'start' and 'size' attributes together convey the physical
 | 
							(RO) The 'start' and 'size' attributes together convey the
 | 
				
			||||||
		address base and number of bytes mapped in the decoder's decode
 | 
							physical address base and number of bytes mapped in the
 | 
				
			||||||
		window. For decoders of devtype "cxl_decoder_root" the address
 | 
							decoder's decode window. For decoders of devtype
 | 
				
			||||||
		range is fixed. For decoders of devtype "cxl_decoder_switch" the
 | 
							"cxl_decoder_root" the address range is fixed. For decoders of
 | 
				
			||||||
		address is bounded by the decode range of the cxl_port ancestor
 | 
							devtype "cxl_decoder_switch" the address is bounded by the
 | 
				
			||||||
		of the decoder's cxl_port, and dynamically updates based on the
 | 
							decode range of the cxl_port ancestor of the decoder's cxl_port,
 | 
				
			||||||
		active memory regions in that address space.
 | 
							and dynamically updates based on the active memory regions in
 | 
				
			||||||
 | 
							that address space.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y/locked
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/locked
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		CXL HDM decoders have the capability to lock the configuration
 | 
							(RO) CXL HDM decoders have the capability to lock the
 | 
				
			||||||
		until the next device reset. For decoders of devtype
 | 
							configuration until the next device reset. For decoders of
 | 
				
			||||||
		"cxl_decoder_root" there is no standard facility to unlock them.
 | 
							devtype "cxl_decoder_root" there is no standard facility to
 | 
				
			||||||
		For decoders of devtype "cxl_decoder_switch" a secondary bus
 | 
							unlock them.  For decoders of devtype "cxl_decoder_switch" a
 | 
				
			||||||
		reset, of the PCIe bridge that provides the bus for this
 | 
							secondary bus reset, of the PCIe bridge that provides the bus
 | 
				
			||||||
		decoders uport, unlocks / resets the decoder.
 | 
							for this decoders uport, unlocks / resets the decoder.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y/target_list
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/target_list
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		Display a comma separated list of the current decoder target
 | 
							(RO) Display a comma separated list of the current decoder
 | 
				
			||||||
		configuration. The list is ordered by the current configured
 | 
							target configuration. The list is ordered by the current
 | 
				
			||||||
		interleave order of the decoder's dport instances. Each entry in
 | 
							configured interleave order of the decoder's dport instances.
 | 
				
			||||||
		the list is a dport id.
 | 
							Each entry in the list is a dport id.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y/cap_{pmem,ram,type2,type3}
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/cap_{pmem,ram,type2,type3}
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		When a CXL decoder is of devtype "cxl_decoder_root", it
 | 
							(RO) When a CXL decoder is of devtype "cxl_decoder_root", it
 | 
				
			||||||
		represents a fixed memory window identified by platform
 | 
							represents a fixed memory window identified by platform
 | 
				
			||||||
		firmware. A fixed window may only support a subset of memory
 | 
							firmware. A fixed window may only support a subset of memory
 | 
				
			||||||
		types. The 'cap_*' attributes indicate whether persistent
 | 
							types. The 'cap_*' attributes indicate whether persistent
 | 
				
			||||||
		memory, volatile memory, accelerator memory, and / or expander
 | 
							memory, volatile memory, accelerator memory, and / or expander
 | 
				
			||||||
		memory may be mapped behind this decoder's memory window.
 | 
							memory may be mapped behind this decoder's memory window.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
What:		/sys/bus/cxl/devices/decoderX.Y/target_type
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/target_type
 | 
				
			||||||
Date:		June, 2021
 | 
					Date:		June, 2021
 | 
				
			||||||
KernelVersion:	v5.14
 | 
					KernelVersion:	v5.14
 | 
				
			||||||
Contact:	linux-cxl@vger.kernel.org
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
Description:
 | 
					Description:
 | 
				
			||||||
		When a CXL decoder is of devtype "cxl_decoder_switch", it can
 | 
							(RO) When a CXL decoder is of devtype "cxl_decoder_switch", it
 | 
				
			||||||
		optionally decode either accelerator memory (type-2) or expander
 | 
							can optionally decode either accelerator memory (type-2) or
 | 
				
			||||||
		memory (type-3). The 'target_type' attribute indicates the
 | 
							expander memory (type-3). The 'target_type' attribute indicates
 | 
				
			||||||
		current setting which may dynamically change based on what
 | 
							the current setting which may dynamically change based on what
 | 
				
			||||||
		memory regions are activated in this decode hierarchy.
 | 
							memory regions are activated in this decode hierarchy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/endpointX/CDAT
 | 
				
			||||||
 | 
					Date:		July, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RO) If this sysfs entry is not present no DOE mailbox was
 | 
				
			||||||
 | 
							found to support CDAT data.  If it is present and the length of
 | 
				
			||||||
 | 
							the data is 0 reading the CDAT data failed.  Otherwise the CDAT
 | 
				
			||||||
 | 
							data is reported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/mode
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) When a CXL decoder is of devtype "cxl_decoder_endpoint" it
 | 
				
			||||||
 | 
							translates from a host physical address range, to a device local
 | 
				
			||||||
 | 
							address range. Device-local address ranges are further split
 | 
				
			||||||
 | 
							into a 'ram' (volatile memory) range and 'pmem' (persistent
 | 
				
			||||||
 | 
							memory) range. The 'mode' attribute emits one of 'ram', 'pmem',
 | 
				
			||||||
 | 
							'mixed', or 'none'. The 'mixed' indication is for error cases
 | 
				
			||||||
 | 
							when a decoder straddles the volatile/persistent partition
 | 
				
			||||||
 | 
							boundary, and 'none' indicates the decoder is not actively
 | 
				
			||||||
 | 
							decoding, or no DPA allocation policy has been set.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							'mode' can be written, when the decoder is in the 'disabled'
 | 
				
			||||||
 | 
							state, with either 'ram' or 'pmem' to set the boundaries for the
 | 
				
			||||||
 | 
							next allocation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/dpa_resource
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RO) When a CXL decoder is of devtype "cxl_decoder_endpoint",
 | 
				
			||||||
 | 
							and its 'dpa_size' attribute is non-zero, this attribute
 | 
				
			||||||
 | 
							indicates the device physical address (DPA) base address of the
 | 
				
			||||||
 | 
							allocation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/dpa_size
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) When a CXL decoder is of devtype "cxl_decoder_endpoint" it
 | 
				
			||||||
 | 
							translates from a host physical address range, to a device local
 | 
				
			||||||
 | 
							address range. The range, base address plus length in bytes, of
 | 
				
			||||||
 | 
							DPA allocated to this decoder is conveyed in these 2 attributes.
 | 
				
			||||||
 | 
							Allocations can be mutated as long as the decoder is in the
 | 
				
			||||||
 | 
							disabled state. A write to 'dpa_size' releases the previous DPA
 | 
				
			||||||
 | 
							allocation and then attempts to allocate from the free capacity
 | 
				
			||||||
 | 
							in the device partition referred to by 'decoderX.Y/mode'.
 | 
				
			||||||
 | 
							Allocate and free requests can only be performed on the highest
 | 
				
			||||||
 | 
							instance number disabled decoder with non-zero size. I.e.
 | 
				
			||||||
 | 
							allocations are enforced to occur in increasing 'decoderX.Y/id'
 | 
				
			||||||
 | 
							order and frees are enforced to occur in decreasing
 | 
				
			||||||
 | 
							'decoderX.Y/id' order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/interleave_ways
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RO) The number of targets across which this decoder's host
 | 
				
			||||||
 | 
							physical address (HPA) memory range is interleaved. The device
 | 
				
			||||||
 | 
							maps every Nth block of HPA (of size ==
 | 
				
			||||||
 | 
							'interleave_granularity') to consecutive DPA addresses. The
 | 
				
			||||||
 | 
							decoder's position in the interleave is determined by the
 | 
				
			||||||
 | 
							device's (endpoint or switch) switch ancestry. For root
 | 
				
			||||||
 | 
							decoders their interleave is specified by platform firmware and
 | 
				
			||||||
 | 
							they only specify a downstream target order for host bridges.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/interleave_granularity
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RO) The number of consecutive bytes of host physical address
 | 
				
			||||||
 | 
							space this decoder claims at address N before the decode rotates
 | 
				
			||||||
 | 
							to the next target in the interleave at address N +
 | 
				
			||||||
 | 
							interleave_granularity (assuming N is aligned to
 | 
				
			||||||
 | 
							interleave_granularity).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/create_pmem_region
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Write a string in the form 'regionZ' to start the process
 | 
				
			||||||
 | 
							of defining a new persistent memory region (interleave-set)
 | 
				
			||||||
 | 
							within the decode range bounded by root decoder 'decoderX.Y'.
 | 
				
			||||||
 | 
							The value written must match the current value returned from
 | 
				
			||||||
 | 
							reading this attribute. An atomic compare exchange operation is
 | 
				
			||||||
 | 
							done on write to assign the requested id to a region and
 | 
				
			||||||
 | 
							allocate the region-id for the next creation attempt. EBUSY is
 | 
				
			||||||
 | 
							returned if the region name written does not match the current
 | 
				
			||||||
 | 
							cached value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/decoderX.Y/delete_region
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(WO) Write a string in the form 'regionZ' to delete that region,
 | 
				
			||||||
 | 
							provided it is currently idle / not bound to a driver.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/uuid
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Write a unique identifier for the region. This field must
 | 
				
			||||||
 | 
							be set for persistent regions and it must not conflict with the
 | 
				
			||||||
 | 
							UUID of another region.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/interleave_granularity
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Set the number of consecutive bytes each device in the
 | 
				
			||||||
 | 
							interleave set will claim. The possible interleave granularity
 | 
				
			||||||
 | 
							values are determined by the CXL spec and the participating
 | 
				
			||||||
 | 
							devices.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/interleave_ways
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Configures the number of devices participating in the
 | 
				
			||||||
 | 
							region is set by writing this value. Each device will provide
 | 
				
			||||||
 | 
							1/interleave_ways of storage for the region.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/size
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) System physical address space to be consumed by the region.
 | 
				
			||||||
 | 
							When written trigger the driver to allocate space out of the
 | 
				
			||||||
 | 
							parent root decoder's address space. When read the size of the
 | 
				
			||||||
 | 
							address space is reported and should match the span of the
 | 
				
			||||||
 | 
							region's resource attribute. Size shall be set after the
 | 
				
			||||||
 | 
							interleave configuration parameters. Once set it cannot be
 | 
				
			||||||
 | 
							changed, only freed by writing 0. The kernel makes no guarantees
 | 
				
			||||||
 | 
							that data is maintained over an address space freeing event, and
 | 
				
			||||||
 | 
							there is no guarantee that a free followed by an allocate
 | 
				
			||||||
 | 
							results in the same address being allocated.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/resource
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RO) A region is a contiguous partition of a CXL root decoder
 | 
				
			||||||
 | 
							address space. Region capacity is allocated by writing to the
 | 
				
			||||||
 | 
							size attribute, the resulting physical address space determined
 | 
				
			||||||
 | 
							by the driver is reflected here. It is therefore not useful to
 | 
				
			||||||
 | 
							read this before writing a value to the size attribute.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/target[0..N]
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Write an endpoint decoder object name to 'targetX' where X
 | 
				
			||||||
 | 
							is the intended position of the endpoint device in the region
 | 
				
			||||||
 | 
							interleave and N is the 'interleave_ways' setting for the
 | 
				
			||||||
 | 
							region. ENXIO is returned if the write results in an impossible
 | 
				
			||||||
 | 
							to map decode scenario, like the endpoint is unreachable at that
 | 
				
			||||||
 | 
							position relative to the root decoder interleave. EBUSY is
 | 
				
			||||||
 | 
							returned if the position in the region is already occupied, or
 | 
				
			||||||
 | 
							if the region is not in a state to accept interleave
 | 
				
			||||||
 | 
							configuration changes. EINVAL is returned if the object name is
 | 
				
			||||||
 | 
							not an endpoint decoder. Once all positions have been
 | 
				
			||||||
 | 
							successfully written a final validation for decode conflicts is
 | 
				
			||||||
 | 
							performed before activating the region.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/cxl/devices/regionZ/commit
 | 
				
			||||||
 | 
					Date:		May, 2022
 | 
				
			||||||
 | 
					KernelVersion:	v5.20
 | 
				
			||||||
 | 
					Contact:	linux-cxl@vger.kernel.org
 | 
				
			||||||
 | 
					Description:
 | 
				
			||||||
 | 
							(RW) Write a boolean 'true' string value to this attribute to
 | 
				
			||||||
 | 
							trigger the region to transition from the software programmed
 | 
				
			||||||
 | 
							state to the actively decoding in hardware state. The commit
 | 
				
			||||||
 | 
							operation in addition to validating that the region is in proper
 | 
				
			||||||
 | 
							configured state, validates that the decoders are being
 | 
				
			||||||
 | 
							committed in spec mandated order (last committed decoder id +
 | 
				
			||||||
 | 
							1), and checks that the hardware accepts the commit request.
 | 
				
			||||||
 | 
							Reading this value indicates whether the region is committed or
 | 
				
			||||||
 | 
							not.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -362,6 +362,14 @@ CXL Core
 | 
				
			||||||
.. kernel-doc:: drivers/cxl/core/mbox.c
 | 
					.. kernel-doc:: drivers/cxl/core/mbox.c
 | 
				
			||||||
   :doc: cxl mbox
 | 
					   :doc: cxl mbox
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CXL Regions
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					.. kernel-doc:: drivers/cxl/core/region.c
 | 
				
			||||||
 | 
					   :doc: cxl core region
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. kernel-doc:: drivers/cxl/core/region.c
 | 
				
			||||||
 | 
					   :identifiers:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
External Interfaces
 | 
					External Interfaces
 | 
				
			||||||
===================
 | 
					===================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,7 @@ int memory_add_physaddr_to_nid(u64 start)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return hot_add_scn_to_nid(start);
 | 
						return hot_add_scn_to_nid(start);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(memory_add_physaddr_to_nid);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int __weak create_section_mapping(unsigned long start, unsigned long end,
 | 
					int __weak create_section_mapping(unsigned long start, unsigned long end,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
menuconfig CXL_BUS
 | 
					menuconfig CXL_BUS
 | 
				
			||||||
	tristate "CXL (Compute Express Link) Devices Support"
 | 
						tristate "CXL (Compute Express Link) Devices Support"
 | 
				
			||||||
	depends on PCI
 | 
						depends on PCI
 | 
				
			||||||
 | 
						select PCI_DOE
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	  CXL is a bus that is electrically compatible with PCI Express, but
 | 
						  CXL is a bus that is electrically compatible with PCI Express, but
 | 
				
			||||||
	  layers three protocols on that signalling (CXL.io, CXL.cache, and
 | 
						  layers three protocols on that signalling (CXL.io, CXL.cache, and
 | 
				
			||||||
| 
						 | 
					@ -102,4 +103,12 @@ config CXL_SUSPEND
 | 
				
			||||||
	def_bool y
 | 
						def_bool y
 | 
				
			||||||
	depends on SUSPEND && CXL_MEM
 | 
						depends on SUSPEND && CXL_MEM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config CXL_REGION
 | 
				
			||||||
 | 
						bool
 | 
				
			||||||
 | 
						default CXL_BUS
 | 
				
			||||||
 | 
						# For MAX_PHYSMEM_BITS
 | 
				
			||||||
 | 
						depends on SPARSEMEM
 | 
				
			||||||
 | 
						select MEMREGION
 | 
				
			||||||
 | 
						select GET_FREE_REGION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,10 +9,6 @@
 | 
				
			||||||
#include "cxlpci.h"
 | 
					#include "cxlpci.h"
 | 
				
			||||||
#include "cxl.h"
 | 
					#include "cxl.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Encode defined in CXL 2.0 8.2.5.12.7 HDM Decoder Control Register */
 | 
					 | 
				
			||||||
#define CFMWS_INTERLEAVE_WAYS(x)	(1 << (x)->interleave_ways)
 | 
					 | 
				
			||||||
#define CFMWS_INTERLEAVE_GRANULARITY(x)	((x)->granularity + 8)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static unsigned long cfmws_to_decoder_flags(int restrictions)
 | 
					static unsigned long cfmws_to_decoder_flags(int restrictions)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned long flags = CXL_DECODER_F_ENABLE;
 | 
						unsigned long flags = CXL_DECODER_F_ENABLE;
 | 
				
			||||||
| 
						 | 
					@ -34,7 +30,8 @@ static unsigned long cfmws_to_decoder_flags(int restrictions)
 | 
				
			||||||
static int cxl_acpi_cfmws_verify(struct device *dev,
 | 
					static int cxl_acpi_cfmws_verify(struct device *dev,
 | 
				
			||||||
				 struct acpi_cedt_cfmws *cfmws)
 | 
									 struct acpi_cedt_cfmws *cfmws)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int expected_len;
 | 
						int rc, expected_len;
 | 
				
			||||||
 | 
						unsigned int ways;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
 | 
						if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
 | 
				
			||||||
		dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
 | 
							dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
 | 
				
			||||||
| 
						 | 
					@ -51,14 +48,14 @@ static int cxl_acpi_cfmws_verify(struct device *dev,
 | 
				
			||||||
		return -EINVAL;
 | 
							return -EINVAL;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (CFMWS_INTERLEAVE_WAYS(cfmws) > CXL_DECODER_MAX_INTERLEAVE) {
 | 
						rc = cxl_to_ways(cfmws->interleave_ways, &ways);
 | 
				
			||||||
		dev_err(dev, "CFMWS Interleave Ways (%d) too large\n",
 | 
						if (rc) {
 | 
				
			||||||
			CFMWS_INTERLEAVE_WAYS(cfmws));
 | 
							dev_err(dev, "CFMWS Interleave Ways (%d) invalid\n",
 | 
				
			||||||
 | 
								cfmws->interleave_ways);
 | 
				
			||||||
		return -EINVAL;
 | 
							return -EINVAL;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expected_len = struct_size((cfmws), interleave_targets,
 | 
						expected_len = struct_size(cfmws, interleave_targets, ways);
 | 
				
			||||||
				   CFMWS_INTERLEAVE_WAYS(cfmws));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (cfmws->header.length < expected_len) {
 | 
						if (cfmws->header.length < expected_len) {
 | 
				
			||||||
		dev_err(dev, "CFMWS length %d less than expected %d\n",
 | 
							dev_err(dev, "CFMWS length %d less than expected %d\n",
 | 
				
			||||||
| 
						 | 
					@ -76,6 +73,8 @@ static int cxl_acpi_cfmws_verify(struct device *dev,
 | 
				
			||||||
struct cxl_cfmws_context {
 | 
					struct cxl_cfmws_context {
 | 
				
			||||||
	struct device *dev;
 | 
						struct device *dev;
 | 
				
			||||||
	struct cxl_port *root_port;
 | 
						struct cxl_port *root_port;
 | 
				
			||||||
 | 
						struct resource *cxl_res;
 | 
				
			||||||
 | 
						int id;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
 | 
					static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
 | 
				
			||||||
| 
						 | 
					@ -84,10 +83,14 @@ static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
 | 
				
			||||||
	int target_map[CXL_DECODER_MAX_INTERLEAVE];
 | 
						int target_map[CXL_DECODER_MAX_INTERLEAVE];
 | 
				
			||||||
	struct cxl_cfmws_context *ctx = arg;
 | 
						struct cxl_cfmws_context *ctx = arg;
 | 
				
			||||||
	struct cxl_port *root_port = ctx->root_port;
 | 
						struct cxl_port *root_port = ctx->root_port;
 | 
				
			||||||
 | 
						struct resource *cxl_res = ctx->cxl_res;
 | 
				
			||||||
 | 
						struct cxl_root_decoder *cxlrd;
 | 
				
			||||||
	struct device *dev = ctx->dev;
 | 
						struct device *dev = ctx->dev;
 | 
				
			||||||
	struct acpi_cedt_cfmws *cfmws;
 | 
						struct acpi_cedt_cfmws *cfmws;
 | 
				
			||||||
	struct cxl_decoder *cxld;
 | 
						struct cxl_decoder *cxld;
 | 
				
			||||||
	int rc, i;
 | 
						unsigned int ways, i, ig;
 | 
				
			||||||
 | 
						struct resource *res;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cfmws = (struct acpi_cedt_cfmws *) header;
 | 
						cfmws = (struct acpi_cedt_cfmws *) header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,19 +102,51 @@ static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (i = 0; i < CFMWS_INTERLEAVE_WAYS(cfmws); i++)
 | 
						rc = cxl_to_ways(cfmws->interleave_ways, &ways);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						rc = cxl_to_granularity(cfmws->granularity, &ig);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						for (i = 0; i < ways; i++)
 | 
				
			||||||
		target_map[i] = cfmws->interleave_targets[i];
 | 
							target_map[i] = cfmws->interleave_targets[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxld = cxl_root_decoder_alloc(root_port, CFMWS_INTERLEAVE_WAYS(cfmws));
 | 
						res = kzalloc(sizeof(*res), GFP_KERNEL);
 | 
				
			||||||
	if (IS_ERR(cxld))
 | 
						if (!res)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res->name = kasprintf(GFP_KERNEL, "CXL Window %d", ctx->id++);
 | 
				
			||||||
 | 
						if (!res->name)
 | 
				
			||||||
 | 
							goto err_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res->start = cfmws->base_hpa;
 | 
				
			||||||
 | 
						res->end = cfmws->base_hpa + cfmws->window_size - 1;
 | 
				
			||||||
 | 
						res->flags = IORESOURCE_MEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* add to the local resource tracking to establish a sort order */
 | 
				
			||||||
 | 
						rc = insert_resource(cxl_res, res);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							goto err_insert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlrd = cxl_root_decoder_alloc(root_port, ways);
 | 
				
			||||||
 | 
						if (IS_ERR(cxlrd))
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxld = &cxlrd->cxlsd.cxld;
 | 
				
			||||||
	cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
 | 
						cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
 | 
				
			||||||
	cxld->target_type = CXL_DECODER_EXPANDER;
 | 
						cxld->target_type = CXL_DECODER_EXPANDER;
 | 
				
			||||||
	cxld->platform_res = (struct resource)DEFINE_RES_MEM(cfmws->base_hpa,
 | 
						cxld->hpa_range = (struct range) {
 | 
				
			||||||
							     cfmws->window_size);
 | 
							.start = res->start,
 | 
				
			||||||
	cxld->interleave_ways = CFMWS_INTERLEAVE_WAYS(cfmws);
 | 
							.end = res->end,
 | 
				
			||||||
	cxld->interleave_granularity = CFMWS_INTERLEAVE_GRANULARITY(cfmws);
 | 
						};
 | 
				
			||||||
 | 
						cxld->interleave_ways = ways;
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Minimize the x1 granularity to advertise support for any
 | 
				
			||||||
 | 
						 * valid region granularity
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (ways == 1)
 | 
				
			||||||
 | 
							ig = CXL_DECODER_MIN_GRANULARITY;
 | 
				
			||||||
 | 
						cxld->interleave_granularity = ig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = cxl_decoder_add(cxld, target_map);
 | 
						rc = cxl_decoder_add(cxld, target_map);
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
| 
						 | 
					@ -119,15 +154,22 @@ static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		rc = cxl_decoder_autoremove(dev, cxld);
 | 
							rc = cxl_decoder_autoremove(dev, cxld);
 | 
				
			||||||
	if (rc) {
 | 
						if (rc) {
 | 
				
			||||||
		dev_err(dev, "Failed to add decoder for %pr\n",
 | 
							dev_err(dev, "Failed to add decode range [%#llx - %#llx]\n",
 | 
				
			||||||
			&cxld->platform_res);
 | 
								cxld->hpa_range.start, cxld->hpa_range.end);
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dev_dbg(dev, "add: %s node: %d range %pr\n", dev_name(&cxld->dev),
 | 
						dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n",
 | 
				
			||||||
		phys_to_target_node(cxld->platform_res.start),
 | 
							dev_name(&cxld->dev),
 | 
				
			||||||
		&cxld->platform_res);
 | 
							phys_to_target_node(cxld->hpa_range.start),
 | 
				
			||||||
 | 
							cxld->hpa_range.start, cxld->hpa_range.end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_insert:
 | 
				
			||||||
 | 
						kfree(res->name);
 | 
				
			||||||
 | 
					err_name:
 | 
				
			||||||
 | 
						kfree(res);
 | 
				
			||||||
 | 
						return -ENOMEM;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__mock struct acpi_device *to_cxl_host_bridge(struct device *host,
 | 
					__mock struct acpi_device *to_cxl_host_bridge(struct device *host,
 | 
				
			||||||
| 
						 | 
					@ -175,8 +217,7 @@ static int add_host_bridge_uport(struct device *match, void *arg)
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	port = devm_cxl_add_port(host, match, dport->component_reg_phys,
 | 
						port = devm_cxl_add_port(host, match, dport->component_reg_phys, dport);
 | 
				
			||||||
				 root_port);
 | 
					 | 
				
			||||||
	if (IS_ERR(port))
 | 
						if (IS_ERR(port))
 | 
				
			||||||
		return PTR_ERR(port);
 | 
							return PTR_ERR(port);
 | 
				
			||||||
	dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev));
 | 
						dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev));
 | 
				
			||||||
| 
						 | 
					@ -282,9 +323,127 @@ static void cxl_acpi_lock_reset_class(void *dev)
 | 
				
			||||||
	device_lock_reset_class(dev);
 | 
						device_lock_reset_class(dev);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void del_cxl_resource(struct resource *res)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						kfree(res->name);
 | 
				
			||||||
 | 
						kfree(res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxl_set_public_resource(struct resource *priv, struct resource *pub)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						priv->desc = (unsigned long) pub;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct resource *cxl_get_public_resource(struct resource *priv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return (struct resource *) priv->desc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void remove_cxl_resources(void *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct resource *res, *next, *cxl = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (res = cxl->child; res; res = next) {
 | 
				
			||||||
 | 
							struct resource *victim = cxl_get_public_resource(res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							next = res->sibling;
 | 
				
			||||||
 | 
							remove_resource(res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (victim) {
 | 
				
			||||||
 | 
								remove_resource(victim);
 | 
				
			||||||
 | 
								kfree(victim);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							del_cxl_resource(res);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource
 | 
				
			||||||
 | 
					 * @cxl_res: A standalone resource tree where each CXL window is a sibling
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Walk each CXL window in @cxl_res and add it to iomem_resource potentially
 | 
				
			||||||
 | 
					 * expanding its boundaries to ensure that any conflicting resources become
 | 
				
			||||||
 | 
					 * children. If a window is expanded it may then conflict with a another window
 | 
				
			||||||
 | 
					 * entry and require the window to be truncated or trimmed. Consider this
 | 
				
			||||||
 | 
					 * situation:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * |-- "CXL Window 0" --||----- "CXL Window 1" -----|
 | 
				
			||||||
 | 
					 * |--------------- "System RAM" -------------|
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ...where platform firmware has established as System RAM resource across 2
 | 
				
			||||||
 | 
					 * windows, but has left some portion of window 1 for dynamic CXL region
 | 
				
			||||||
 | 
					 * provisioning. In this case "Window 0" will span the entirety of the "System
 | 
				
			||||||
 | 
					 * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
 | 
				
			||||||
 | 
					 * of that "System RAM" resource.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int add_cxl_resources(struct resource *cxl_res)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct resource *res, *new, *next;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (res = cxl_res->child; res; res = next) {
 | 
				
			||||||
 | 
							new = kzalloc(sizeof(*new), GFP_KERNEL);
 | 
				
			||||||
 | 
							if (!new)
 | 
				
			||||||
 | 
								return -ENOMEM;
 | 
				
			||||||
 | 
							new->name = res->name;
 | 
				
			||||||
 | 
							new->start = res->start;
 | 
				
			||||||
 | 
							new->end = res->end;
 | 
				
			||||||
 | 
							new->flags = IORESOURCE_MEM;
 | 
				
			||||||
 | 
							new->desc = IORES_DESC_CXL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * Record the public resource in the private cxl_res tree for
 | 
				
			||||||
 | 
							 * later removal.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							cxl_set_public_resource(res, new);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							insert_resource_expand_to_fit(&iomem_resource, new);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							next = res->sibling;
 | 
				
			||||||
 | 
							while (next && resource_overlaps(new, next)) {
 | 
				
			||||||
 | 
								if (resource_contains(new, next)) {
 | 
				
			||||||
 | 
									struct resource *_next = next->sibling;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									remove_resource(next);
 | 
				
			||||||
 | 
									del_cxl_resource(next);
 | 
				
			||||||
 | 
									next = _next;
 | 
				
			||||||
 | 
								} else
 | 
				
			||||||
 | 
									next->start = new->end + 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pair_cxl_resource(struct device *dev, void *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct resource *cxl_res = data;
 | 
				
			||||||
 | 
						struct resource *p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!is_root_decoder(dev))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (p = cxl_res->child; p; p = p->sibling) {
 | 
				
			||||||
 | 
							struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
 | 
				
			||||||
 | 
							struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
 | 
				
			||||||
 | 
							struct resource res = {
 | 
				
			||||||
 | 
								.start = cxld->hpa_range.start,
 | 
				
			||||||
 | 
								.end = cxld->hpa_range.end,
 | 
				
			||||||
 | 
								.flags = IORESOURCE_MEM,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (resource_contains(p, &res)) {
 | 
				
			||||||
 | 
								cxlrd->res = cxl_get_public_resource(p);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_acpi_probe(struct platform_device *pdev)
 | 
					static int cxl_acpi_probe(struct platform_device *pdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int rc;
 | 
						int rc;
 | 
				
			||||||
 | 
						struct resource *cxl_res;
 | 
				
			||||||
	struct cxl_port *root_port;
 | 
						struct cxl_port *root_port;
 | 
				
			||||||
	struct device *host = &pdev->dev;
 | 
						struct device *host = &pdev->dev;
 | 
				
			||||||
	struct acpi_device *adev = ACPI_COMPANION(host);
 | 
						struct acpi_device *adev = ACPI_COMPANION(host);
 | 
				
			||||||
| 
						 | 
					@ -296,6 +455,14 @@ static int cxl_acpi_probe(struct platform_device *pdev)
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxl_res = devm_kzalloc(host, sizeof(*cxl_res), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!cxl_res)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
						cxl_res->name = "CXL mem";
 | 
				
			||||||
 | 
						cxl_res->start = 0;
 | 
				
			||||||
 | 
						cxl_res->end = -1;
 | 
				
			||||||
 | 
						cxl_res->flags = IORESOURCE_MEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
 | 
						root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
 | 
				
			||||||
	if (IS_ERR(root_port))
 | 
						if (IS_ERR(root_port))
 | 
				
			||||||
		return PTR_ERR(root_port);
 | 
							return PTR_ERR(root_port);
 | 
				
			||||||
| 
						 | 
					@ -306,11 +473,28 @@ static int cxl_acpi_probe(struct platform_device *pdev)
 | 
				
			||||||
	if (rc < 0)
 | 
						if (rc < 0)
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(host, remove_cxl_resources, cxl_res);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx = (struct cxl_cfmws_context) {
 | 
						ctx = (struct cxl_cfmws_context) {
 | 
				
			||||||
		.dev = host,
 | 
							.dev = host,
 | 
				
			||||||
		.root_port = root_port,
 | 
							.root_port = root_port,
 | 
				
			||||||
 | 
							.cxl_res = cxl_res,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx);
 | 
						rc = acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx);
 | 
				
			||||||
 | 
						if (rc < 0)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = add_cxl_resources(cxl_res);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Populate the root decoders with their related iomem resource,
 | 
				
			||||||
 | 
						 * if present
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						device_for_each_child(&root_port->dev, cxl_res, pair_cxl_resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Root level scanned with host-bridge as dports, now scan host-bridges
 | 
						 * Root level scanned with host-bridge as dports, now scan host-bridges
 | 
				
			||||||
| 
						 | 
					@ -337,12 +521,19 @@ static const struct acpi_device_id cxl_acpi_ids[] = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
 | 
					MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct platform_device_id cxl_test_ids[] = {
 | 
				
			||||||
 | 
						{ "cxl_acpi" },
 | 
				
			||||||
 | 
						{ },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(platform, cxl_test_ids);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct platform_driver cxl_acpi_driver = {
 | 
					static struct platform_driver cxl_acpi_driver = {
 | 
				
			||||||
	.probe = cxl_acpi_probe,
 | 
						.probe = cxl_acpi_probe,
 | 
				
			||||||
	.driver = {
 | 
						.driver = {
 | 
				
			||||||
		.name = KBUILD_MODNAME,
 | 
							.name = KBUILD_MODNAME,
 | 
				
			||||||
		.acpi_match_table = cxl_acpi_ids,
 | 
							.acpi_match_table = cxl_acpi_ids,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						.id_table = cxl_test_ids,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module_platform_driver(cxl_acpi_driver);
 | 
					module_platform_driver(cxl_acpi_driver);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,3 +10,4 @@ cxl_core-y += memdev.o
 | 
				
			||||||
cxl_core-y += mbox.o
 | 
					cxl_core-y += mbox.o
 | 
				
			||||||
cxl_core-y += pci.o
 | 
					cxl_core-y += pci.o
 | 
				
			||||||
cxl_core-y += hdm.o
 | 
					cxl_core-y += hdm.o
 | 
				
			||||||
 | 
					cxl_core-$(CONFIG_CXL_REGION) += region.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,36 @@ extern const struct device_type cxl_nvdimm_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern struct attribute_group cxl_base_attribute_group;
 | 
					extern struct attribute_group cxl_base_attribute_group;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_CXL_REGION
 | 
				
			||||||
 | 
					extern struct device_attribute dev_attr_create_pmem_region;
 | 
				
			||||||
 | 
					extern struct device_attribute dev_attr_delete_region;
 | 
				
			||||||
 | 
					extern struct device_attribute dev_attr_region;
 | 
				
			||||||
 | 
					extern const struct device_type cxl_pmem_region_type;
 | 
				
			||||||
 | 
					extern const struct device_type cxl_region_type;
 | 
				
			||||||
 | 
					void cxl_decoder_kill_region(struct cxl_endpoint_decoder *cxled);
 | 
				
			||||||
 | 
					#define CXL_REGION_ATTR(x) (&dev_attr_##x.attr)
 | 
				
			||||||
 | 
					#define CXL_REGION_TYPE(x) (&cxl_region_type)
 | 
				
			||||||
 | 
					#define SET_CXL_REGION_ATTR(x) (&dev_attr_##x.attr),
 | 
				
			||||||
 | 
					#define CXL_PMEM_REGION_TYPE(x) (&cxl_pmem_region_type)
 | 
				
			||||||
 | 
					int cxl_region_init(void);
 | 
				
			||||||
 | 
					void cxl_region_exit(void);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static inline void cxl_decoder_kill_region(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					static inline int cxl_region_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					static inline void cxl_region_exit(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#define CXL_REGION_ATTR(x) NULL
 | 
				
			||||||
 | 
					#define CXL_REGION_TYPE(x) NULL
 | 
				
			||||||
 | 
					#define SET_CXL_REGION_ATTR(x)
 | 
				
			||||||
 | 
					#define CXL_PMEM_REGION_TYPE(x) NULL
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_send_command;
 | 
					struct cxl_send_command;
 | 
				
			||||||
struct cxl_mem_query_commands;
 | 
					struct cxl_mem_query_commands;
 | 
				
			||||||
int cxl_query_cmd(struct cxl_memdev *cxlmd,
 | 
					int cxl_query_cmd(struct cxl_memdev *cxlmd,
 | 
				
			||||||
| 
						 | 
					@ -17,9 +47,28 @@ int cxl_send_cmd(struct cxl_memdev *cxlmd, struct cxl_send_command __user *s);
 | 
				
			||||||
void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
 | 
					void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
 | 
				
			||||||
				   resource_size_t length);
 | 
									   resource_size_t length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct dentry *cxl_debugfs_create_dir(const char *dir);
 | 
				
			||||||
 | 
					int cxl_dpa_set_mode(struct cxl_endpoint_decoder *cxled,
 | 
				
			||||||
 | 
							     enum cxl_decoder_mode mode);
 | 
				
			||||||
 | 
					int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, unsigned long long size);
 | 
				
			||||||
 | 
					int cxl_dpa_free(struct cxl_endpoint_decoder *cxled);
 | 
				
			||||||
 | 
					resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled);
 | 
				
			||||||
 | 
					resource_size_t cxl_dpa_resource_start(struct cxl_endpoint_decoder *cxled);
 | 
				
			||||||
 | 
					extern struct rw_semaphore cxl_dpa_rwsem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool is_switch_decoder(struct device *dev);
 | 
				
			||||||
 | 
					struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);
 | 
				
			||||||
 | 
					static inline struct cxl_ep *cxl_ep_load(struct cxl_port *port,
 | 
				
			||||||
 | 
										 struct cxl_memdev *cxlmd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!port)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return xa_load(&port->endpoints, (unsigned long)&cxlmd->dev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int cxl_memdev_init(void);
 | 
					int cxl_memdev_init(void);
 | 
				
			||||||
void cxl_memdev_exit(void);
 | 
					void cxl_memdev_exit(void);
 | 
				
			||||||
void cxl_mbox_init(void);
 | 
					void cxl_mbox_init(void);
 | 
				
			||||||
void cxl_mbox_exit(void);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* __CXL_CORE_H__ */
 | 
					#endif /* __CXL_CORE_H__ */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
// SPDX-License-Identifier: GPL-2.0-only
 | 
					// SPDX-License-Identifier: GPL-2.0-only
 | 
				
			||||||
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
 | 
					/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
 | 
				
			||||||
#include <linux/io-64-nonatomic-hi-lo.h>
 | 
					#include <linux/io-64-nonatomic-hi-lo.h>
 | 
				
			||||||
 | 
					#include <linux/seq_file.h>
 | 
				
			||||||
#include <linux/device.h>
 | 
					#include <linux/device.h>
 | 
				
			||||||
#include <linux/delay.h>
 | 
					#include <linux/delay.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +17,8 @@
 | 
				
			||||||
 * for enumerating these registers and capabilities.
 | 
					 * for enumerating these registers and capabilities.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DECLARE_RWSEM(cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
					static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
				
			||||||
			   int *target_map)
 | 
								   int *target_map)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -46,20 +49,22 @@ static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
int devm_cxl_add_passthrough_decoder(struct cxl_port *port)
 | 
					int devm_cxl_add_passthrough_decoder(struct cxl_port *port)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_decoder *cxld;
 | 
						struct cxl_switch_decoder *cxlsd;
 | 
				
			||||||
	struct cxl_dport *dport;
 | 
						struct cxl_dport *dport = NULL;
 | 
				
			||||||
	int single_port_map[1];
 | 
						int single_port_map[1];
 | 
				
			||||||
 | 
						unsigned long index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxld = cxl_switch_decoder_alloc(port, 1);
 | 
						cxlsd = cxl_switch_decoder_alloc(port, 1);
 | 
				
			||||||
	if (IS_ERR(cxld))
 | 
						if (IS_ERR(cxlsd))
 | 
				
			||||||
		return PTR_ERR(cxld);
 | 
							return PTR_ERR(cxlsd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device_lock_assert(&port->dev);
 | 
						device_lock_assert(&port->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dport = list_first_entry(&port->dports, typeof(*dport), list);
 | 
						xa_for_each(&port->dports, index, dport)
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	single_port_map[0] = dport->port_id;
 | 
						single_port_map[0] = dport->port_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return add_hdm_decoder(port, cxld, single_port_map);
 | 
						return add_hdm_decoder(port, &cxlsd->cxld, single_port_map);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_passthrough_decoder, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(devm_cxl_add_passthrough_decoder, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,47 +129,577 @@ struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port)
 | 
				
			||||||
		return ERR_PTR(-ENXIO);
 | 
							return ERR_PTR(-ENXIO);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_set_drvdata(dev, cxlhdm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return cxlhdm;
 | 
						return cxlhdm;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_hdm, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_hdm, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int to_interleave_granularity(u32 ctrl)
 | 
					static void __cxl_dpa_debug(struct seq_file *file, struct resource *r, int depth)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int val = FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl);
 | 
						unsigned long long start = r->start, end = r->end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 256 << val;
 | 
						seq_printf(file, "%*s%08llx-%08llx : %s\n", depth * 2, "", start, end,
 | 
				
			||||||
 | 
							   r->name);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int to_interleave_ways(u32 ctrl)
 | 
					void cxl_dpa_debug(struct seq_file *file, struct cxl_dev_state *cxlds)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int val = FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl);
 | 
						struct resource *p1, *p2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (val) {
 | 
						down_read(&cxl_dpa_rwsem);
 | 
				
			||||||
	case 0 ... 4:
 | 
						for (p1 = cxlds->dpa_res.child; p1; p1 = p1->sibling) {
 | 
				
			||||||
		return 1 << val;
 | 
							__cxl_dpa_debug(file, p1, 0);
 | 
				
			||||||
	case 8 ... 10:
 | 
							for (p2 = p1->child; p2; p2 = p2->sibling)
 | 
				
			||||||
		return 3 << (val - 8);
 | 
								__cxl_dpa_debug(file, p2, 1);
 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		return 0;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						up_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_NS_GPL(cxl_dpa_debug, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Must be called in a context that synchronizes against this decoder's
 | 
				
			||||||
 | 
					 * port ->remove() callback (like an endpoint decoder sysfs attribute)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void __cxl_dpa_release(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
						struct resource *res = cxled->dpa_res;
 | 
				
			||||||
 | 
						resource_size_t skip_start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lockdep_assert_held_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* save @skip_start, before @res is released */
 | 
				
			||||||
 | 
						skip_start = res->start - cxled->skip;
 | 
				
			||||||
 | 
						__release_region(&cxlds->dpa_res, res->start, resource_size(res));
 | 
				
			||||||
 | 
						if (cxled->skip)
 | 
				
			||||||
 | 
							__release_region(&cxlds->dpa_res, skip_start, cxled->skip);
 | 
				
			||||||
 | 
						cxled->skip = 0;
 | 
				
			||||||
 | 
						cxled->dpa_res = NULL;
 | 
				
			||||||
 | 
						put_device(&cxled->cxld.dev);
 | 
				
			||||||
 | 
						port->hdm_end--;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxl_dpa_release(void *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						down_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						__cxl_dpa_release(cxled);
 | 
				
			||||||
 | 
						up_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Must be called from context that will not race port device
 | 
				
			||||||
 | 
					 * unregistration, like decoder sysfs attribute methods
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void devm_cxl_dpa_release(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lockdep_assert_held_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						devm_remove_action(&port->dev, cxl_dpa_release, cxled);
 | 
				
			||||||
 | 
						__cxl_dpa_release(cxled);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
 | 
				
			||||||
 | 
								     resource_size_t base, resource_size_t len,
 | 
				
			||||||
 | 
								     resource_size_t skipped)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
						struct device *dev = &port->dev;
 | 
				
			||||||
 | 
						struct resource *res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lockdep_assert_held_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!len)
 | 
				
			||||||
 | 
							goto success;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxled->dpa_res) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder%d.%d: existing allocation %pr assigned\n",
 | 
				
			||||||
 | 
								port->id, cxled->cxld.id, cxled->dpa_res);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (port->hdm_end + 1 != cxled->cxld.id) {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * Assumes alloc and commit order is always in hardware instance
 | 
				
			||||||
 | 
							 * order per expectations from 8.2.5.12.20 Committing Decoder
 | 
				
			||||||
 | 
							 * Programming that enforce decoder[m] committed before
 | 
				
			||||||
 | 
							 * decoder[m+1] commit start.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder%d.%d: expected decoder%d.%d\n", port->id,
 | 
				
			||||||
 | 
								cxled->cxld.id, port->id, port->hdm_end + 1);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (skipped) {
 | 
				
			||||||
 | 
							res = __request_region(&cxlds->dpa_res, base - skipped, skipped,
 | 
				
			||||||
 | 
									       dev_name(&cxled->cxld.dev), 0);
 | 
				
			||||||
 | 
							if (!res) {
 | 
				
			||||||
 | 
								dev_dbg(dev,
 | 
				
			||||||
 | 
									"decoder%d.%d: failed to reserve skipped space\n",
 | 
				
			||||||
 | 
									port->id, cxled->cxld.id);
 | 
				
			||||||
 | 
								return -EBUSY;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res = __request_region(&cxlds->dpa_res, base, len,
 | 
				
			||||||
 | 
								       dev_name(&cxled->cxld.dev), 0);
 | 
				
			||||||
 | 
						if (!res) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder%d.%d: failed to reserve allocation\n",
 | 
				
			||||||
 | 
								port->id, cxled->cxld.id);
 | 
				
			||||||
 | 
							if (skipped)
 | 
				
			||||||
 | 
								__release_region(&cxlds->dpa_res, base - skipped,
 | 
				
			||||||
 | 
										 skipped);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cxled->dpa_res = res;
 | 
				
			||||||
 | 
						cxled->skip = skipped;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (resource_contains(&cxlds->pmem_res, res))
 | 
				
			||||||
 | 
							cxled->mode = CXL_DECODER_PMEM;
 | 
				
			||||||
 | 
						else if (resource_contains(&cxlds->ram_res, res))
 | 
				
			||||||
 | 
							cxled->mode = CXL_DECODER_RAM;
 | 
				
			||||||
 | 
						else {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder%d.%d: %pr mixed\n", port->id,
 | 
				
			||||||
 | 
								cxled->cxld.id, cxled->dpa_res);
 | 
				
			||||||
 | 
							cxled->mode = CXL_DECODER_MIXED;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					success:
 | 
				
			||||||
 | 
						port->hdm_end++;
 | 
				
			||||||
 | 
						get_device(&cxled->cxld.dev);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int devm_cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
 | 
				
			||||||
 | 
									resource_size_t base, resource_size_t len,
 | 
				
			||||||
 | 
									resource_size_t skipped)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						rc = __cxl_dpa_reserve(cxled, base, len, skipped);
 | 
				
			||||||
 | 
						up_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						resource_size_t size = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						if (cxled->dpa_res)
 | 
				
			||||||
 | 
							size = resource_size(cxled->dpa_res);
 | 
				
			||||||
 | 
						up_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					resource_size_t cxl_dpa_resource_start(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						resource_size_t base = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						if (cxled->dpa_res)
 | 
				
			||||||
 | 
							base = cxled->dpa_res->start;
 | 
				
			||||||
 | 
						up_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return base;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int cxl_dpa_free(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
						struct device *dev = &cxled->cxld.dev;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						if (!cxled->dpa_res) {
 | 
				
			||||||
 | 
							rc = 0;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cxled->cxld.region) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder assigned to: %s\n",
 | 
				
			||||||
 | 
								dev_name(&cxled->cxld.region->dev));
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder enabled\n");
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (cxled->cxld.id != port->hdm_end) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "expected decoder%d.%d\n", port->id,
 | 
				
			||||||
 | 
								port->hdm_end);
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						devm_cxl_dpa_release(cxled);
 | 
				
			||||||
 | 
						rc = 0;
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						up_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						return rc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int cxl_dpa_set_mode(struct cxl_endpoint_decoder *cxled,
 | 
				
			||||||
 | 
							     enum cxl_decoder_mode mode)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 | 
				
			||||||
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
						struct device *dev = &cxled->cxld.dev;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (mode) {
 | 
				
			||||||
 | 
						case CXL_DECODER_RAM:
 | 
				
			||||||
 | 
						case CXL_DECODER_PMEM:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							dev_dbg(dev, "unsupported mode: %d\n", mode);
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Only allow modes that are supported by the current partition
 | 
				
			||||||
 | 
						 * configuration
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (mode == CXL_DECODER_PMEM && !resource_size(&cxlds->pmem_res)) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "no available pmem capacity\n");
 | 
				
			||||||
 | 
							rc = -ENXIO;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (mode == CXL_DECODER_RAM && !resource_size(&cxlds->ram_res)) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "no available ram capacity\n");
 | 
				
			||||||
 | 
							rc = -ENXIO;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxled->mode = mode;
 | 
				
			||||||
 | 
						rc = 0;
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						up_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, unsigned long long size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 | 
				
			||||||
 | 
						resource_size_t free_ram_start, free_pmem_start;
 | 
				
			||||||
 | 
						struct cxl_port *port = cxled_to_port(cxled);
 | 
				
			||||||
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
						struct device *dev = &cxled->cxld.dev;
 | 
				
			||||||
 | 
						resource_size_t start, avail, skip;
 | 
				
			||||||
 | 
						struct resource *p, *last;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						if (cxled->cxld.region) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder attached to %s\n",
 | 
				
			||||||
 | 
								dev_name(&cxled->cxld.region->dev));
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "decoder enabled\n");
 | 
				
			||||||
 | 
							rc = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (p = cxlds->ram_res.child, last = NULL; p; p = p->sibling)
 | 
				
			||||||
 | 
							last = p;
 | 
				
			||||||
 | 
						if (last)
 | 
				
			||||||
 | 
							free_ram_start = last->end + 1;
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							free_ram_start = cxlds->ram_res.start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (p = cxlds->pmem_res.child, last = NULL; p; p = p->sibling)
 | 
				
			||||||
 | 
							last = p;
 | 
				
			||||||
 | 
						if (last)
 | 
				
			||||||
 | 
							free_pmem_start = last->end + 1;
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							free_pmem_start = cxlds->pmem_res.start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxled->mode == CXL_DECODER_RAM) {
 | 
				
			||||||
 | 
							start = free_ram_start;
 | 
				
			||||||
 | 
							avail = cxlds->ram_res.end - start + 1;
 | 
				
			||||||
 | 
							skip = 0;
 | 
				
			||||||
 | 
						} else if (cxled->mode == CXL_DECODER_PMEM) {
 | 
				
			||||||
 | 
							resource_size_t skip_start, skip_end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							start = free_pmem_start;
 | 
				
			||||||
 | 
							avail = cxlds->pmem_res.end - start + 1;
 | 
				
			||||||
 | 
							skip_start = free_ram_start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * If some pmem is already allocated, then that allocation
 | 
				
			||||||
 | 
							 * already handled the skip.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (cxlds->pmem_res.child &&
 | 
				
			||||||
 | 
							    skip_start == cxlds->pmem_res.child->start)
 | 
				
			||||||
 | 
								skip_end = skip_start - 1;
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								skip_end = start - 1;
 | 
				
			||||||
 | 
							skip = skip_end - skip_start + 1;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							dev_dbg(dev, "mode not set\n");
 | 
				
			||||||
 | 
							rc = -EINVAL;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (size > avail) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "%pa exceeds available %s capacity: %pa\n", &size,
 | 
				
			||||||
 | 
								cxled->mode == CXL_DECODER_RAM ? "ram" : "pmem",
 | 
				
			||||||
 | 
								&avail);
 | 
				
			||||||
 | 
							rc = -ENOSPC;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = __cxl_dpa_reserve(cxled, start, size, skip);
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						up_write(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u16 eig;
 | 
				
			||||||
 | 
						u8 eiw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Input validation ensures these warns never fire, but otherwise
 | 
				
			||||||
 | 
						 * suppress unititalized variable usage warnings.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (WARN_ONCE(ways_to_cxl(cxld->interleave_ways, &eiw),
 | 
				
			||||||
 | 
							      "invalid interleave_ways: %d\n", cxld->interleave_ways))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						if (WARN_ONCE(granularity_to_cxl(cxld->interleave_granularity, &eig),
 | 
				
			||||||
 | 
							      "invalid interleave_granularity: %d\n",
 | 
				
			||||||
 | 
							      cxld->interleave_granularity))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
 | 
				
			||||||
 | 
						u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
 | 
				
			||||||
 | 
						*ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32p_replace_bits(ctrl, !!(cxld->target_type == 3),
 | 
				
			||||||
 | 
								  CXL_HDM_DECODER0_CTRL_TYPE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_dport **t = &cxlsd->target[0];
 | 
				
			||||||
 | 
						int ways = cxlsd->cxld.interleave_ways;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (dev_WARN_ONCE(&cxlsd->cxld.dev,
 | 
				
			||||||
 | 
								  ways > 8 || ways > cxlsd->nr_targets,
 | 
				
			||||||
 | 
								  "ways: %d overflows targets: %d\n", ways,
 | 
				
			||||||
 | 
								  cxlsd->nr_targets))
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*tgt = FIELD_PREP(GENMASK(7, 0), t[0]->port_id);
 | 
				
			||||||
 | 
						if (ways > 1)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK(15, 8), t[1]->port_id);
 | 
				
			||||||
 | 
						if (ways > 2)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK(23, 16), t[2]->port_id);
 | 
				
			||||||
 | 
						if (ways > 3)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK(31, 24), t[3]->port_id);
 | 
				
			||||||
 | 
						if (ways > 4)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK_ULL(39, 32), t[4]->port_id);
 | 
				
			||||||
 | 
						if (ways > 5)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK_ULL(47, 40), t[5]->port_id);
 | 
				
			||||||
 | 
						if (ways > 6)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK_ULL(55, 48), t[6]->port_id);
 | 
				
			||||||
 | 
						if (ways > 7)
 | 
				
			||||||
 | 
							*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
 | 
				
			||||||
 | 
					 * committed or error within 10ms, but just be generous with 20ms to account for
 | 
				
			||||||
 | 
					 * clock skew and other marginal behavior
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define COMMIT_TIMEOUT_MS 20
 | 
				
			||||||
 | 
					static int cxld_await_commit(void __iomem *hdm, int id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 ctrl;
 | 
				
			||||||
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
 | 
				
			||||||
 | 
							ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
 | 
				
			||||||
 | 
							if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
 | 
				
			||||||
 | 
								ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
 | 
				
			||||||
 | 
								writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
 | 
				
			||||||
 | 
								return -EIO;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							fsleep(1000);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return -ETIMEDOUT;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_decoder_commit(struct cxl_decoder *cxld)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(cxld->dev.parent);
 | 
				
			||||||
 | 
						struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
 | 
				
			||||||
 | 
						void __iomem *hdm = cxlhdm->regs.hdm_decoder;
 | 
				
			||||||
 | 
						int id = cxld->id, rc;
 | 
				
			||||||
 | 
						u64 base, size;
 | 
				
			||||||
 | 
						u32 ctrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxld->flags & CXL_DECODER_F_ENABLE)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (port->commit_end + 1 != id) {
 | 
				
			||||||
 | 
							dev_dbg(&port->dev,
 | 
				
			||||||
 | 
								"%s: out of order commit, expected decoder%d.%d\n",
 | 
				
			||||||
 | 
								dev_name(&cxld->dev), port->id, port->commit_end + 1);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						/* common decoder settings */
 | 
				
			||||||
 | 
						ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
 | 
				
			||||||
 | 
						cxld_set_interleave(cxld, &ctrl);
 | 
				
			||||||
 | 
						cxld_set_type(cxld, &ctrl);
 | 
				
			||||||
 | 
						base = cxld->hpa_range.start;
 | 
				
			||||||
 | 
						size = range_len(&cxld->hpa_range);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
 | 
				
			||||||
 | 
						writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
 | 
				
			||||||
 | 
						writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
 | 
				
			||||||
 | 
						writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (is_switch_decoder(&cxld->dev)) {
 | 
				
			||||||
 | 
							struct cxl_switch_decoder *cxlsd =
 | 
				
			||||||
 | 
								to_cxl_switch_decoder(&cxld->dev);
 | 
				
			||||||
 | 
							void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
 | 
				
			||||||
 | 
							void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
 | 
				
			||||||
 | 
							u64 targets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rc = cxlsd_set_targets(cxlsd, &targets);
 | 
				
			||||||
 | 
							if (rc) {
 | 
				
			||||||
 | 
								dev_dbg(&port->dev, "%s: target configuration error\n",
 | 
				
			||||||
 | 
									dev_name(&cxld->dev));
 | 
				
			||||||
 | 
								goto err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							writel(upper_32_bits(targets), tl_hi);
 | 
				
			||||||
 | 
							writel(lower_32_bits(targets), tl_lo);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							struct cxl_endpoint_decoder *cxled =
 | 
				
			||||||
 | 
								to_cxl_endpoint_decoder(&cxld->dev);
 | 
				
			||||||
 | 
							void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
 | 
				
			||||||
 | 
							void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							writel(upper_32_bits(cxled->skip), sk_hi);
 | 
				
			||||||
 | 
							writel(lower_32_bits(cxled->skip), sk_lo);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
 | 
				
			||||||
 | 
						up_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->commit_end++;
 | 
				
			||||||
 | 
						rc = cxld_await_commit(hdm, cxld->id);
 | 
				
			||||||
 | 
					err:
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							dev_dbg(&port->dev, "%s: error %d committing decoder\n",
 | 
				
			||||||
 | 
								dev_name(&cxld->dev), rc);
 | 
				
			||||||
 | 
							cxld->reset(cxld);
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cxld->flags |= CXL_DECODER_F_ENABLE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_decoder_reset(struct cxl_decoder *cxld)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(cxld->dev.parent);
 | 
				
			||||||
 | 
						struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
 | 
				
			||||||
 | 
						void __iomem *hdm = cxlhdm->regs.hdm_decoder;
 | 
				
			||||||
 | 
						int id = cxld->id;
 | 
				
			||||||
 | 
						u32 ctrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (port->commit_end != id) {
 | 
				
			||||||
 | 
							dev_dbg(&port->dev,
 | 
				
			||||||
 | 
								"%s: out of order reset, expected decoder%d.%d\n",
 | 
				
			||||||
 | 
								dev_name(&cxld->dev), port->id, port->commit_end);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						down_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
						ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
 | 
				
			||||||
 | 
						ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
 | 
				
			||||||
 | 
						writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writel(0, hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
 | 
				
			||||||
 | 
						writel(0, hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
 | 
				
			||||||
 | 
						writel(0, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
 | 
				
			||||||
 | 
						writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
 | 
				
			||||||
 | 
						up_read(&cxl_dpa_rwsem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->commit_end--;
 | 
				
			||||||
 | 
						cxld->flags &= ~CXL_DECODER_F_ENABLE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
					static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
				
			||||||
			    int *target_map, void __iomem *hdm, int which)
 | 
								    int *target_map, void __iomem *hdm, int which,
 | 
				
			||||||
 | 
								    u64 *dpa_base)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	u64 size, base;
 | 
						struct cxl_endpoint_decoder *cxled = NULL;
 | 
				
			||||||
 | 
						u64 size, base, skip, dpa_size;
 | 
				
			||||||
 | 
						bool committed;
 | 
				
			||||||
 | 
						u32 remainder;
 | 
				
			||||||
 | 
						int i, rc;
 | 
				
			||||||
	u32 ctrl;
 | 
						u32 ctrl;
 | 
				
			||||||
	int i;
 | 
					 | 
				
			||||||
	union {
 | 
						union {
 | 
				
			||||||
		u64 value;
 | 
							u64 value;
 | 
				
			||||||
		unsigned char target_id[8];
 | 
							unsigned char target_id[8];
 | 
				
			||||||
	} target_list;
 | 
						} target_list;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (is_endpoint_decoder(&cxld->dev))
 | 
				
			||||||
 | 
							cxled = to_cxl_endpoint_decoder(&cxld->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
 | 
						ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
 | 
				
			||||||
	base = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which));
 | 
						base = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which));
 | 
				
			||||||
	size = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which));
 | 
						size = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which));
 | 
				
			||||||
 | 
						committed = !!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED);
 | 
				
			||||||
 | 
						cxld->commit = cxl_decoder_commit;
 | 
				
			||||||
 | 
						cxld->reset = cxl_decoder_reset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED))
 | 
						if (!committed)
 | 
				
			||||||
		size = 0;
 | 
							size = 0;
 | 
				
			||||||
	if (base == U64_MAX || size == U64_MAX) {
 | 
						if (base == U64_MAX || size == U64_MAX) {
 | 
				
			||||||
		dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
 | 
							dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
 | 
				
			||||||
| 
						 | 
					@ -172,39 +707,77 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
 | 
				
			||||||
		return -ENXIO;
 | 
							return -ENXIO;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxld->decoder_range = (struct range) {
 | 
						cxld->hpa_range = (struct range) {
 | 
				
			||||||
		.start = base,
 | 
							.start = base,
 | 
				
			||||||
		.end = base + size - 1,
 | 
							.end = base + size - 1,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* switch decoders are always enabled if committed */
 | 
						/* decoders are enabled if committed */
 | 
				
			||||||
	if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
 | 
						if (committed) {
 | 
				
			||||||
		cxld->flags |= CXL_DECODER_F_ENABLE;
 | 
							cxld->flags |= CXL_DECODER_F_ENABLE;
 | 
				
			||||||
		if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
 | 
							if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
 | 
				
			||||||
			cxld->flags |= CXL_DECODER_F_LOCK;
 | 
								cxld->flags |= CXL_DECODER_F_LOCK;
 | 
				
			||||||
 | 
							if (FIELD_GET(CXL_HDM_DECODER0_CTRL_TYPE, ctrl))
 | 
				
			||||||
 | 
								cxld->target_type = CXL_DECODER_EXPANDER;
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								cxld->target_type = CXL_DECODER_ACCELERATOR;
 | 
				
			||||||
 | 
							if (cxld->id != port->commit_end + 1) {
 | 
				
			||||||
 | 
								dev_warn(&port->dev,
 | 
				
			||||||
 | 
									 "decoder%d.%d: Committed out of order\n",
 | 
				
			||||||
 | 
									 port->id, cxld->id);
 | 
				
			||||||
 | 
								return -ENXIO;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							port->commit_end = cxld->id;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							/* unless / until type-2 drivers arrive, assume type-3 */
 | 
				
			||||||
 | 
							if (FIELD_GET(CXL_HDM_DECODER0_CTRL_TYPE, ctrl) == 0) {
 | 
				
			||||||
 | 
								ctrl |= CXL_HDM_DECODER0_CTRL_TYPE;
 | 
				
			||||||
 | 
								writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cxld->target_type = CXL_DECODER_EXPANDER;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cxld->interleave_ways = to_interleave_ways(ctrl);
 | 
						rc = cxl_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
 | 
				
			||||||
	if (!cxld->interleave_ways) {
 | 
								 &cxld->interleave_ways);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
		dev_warn(&port->dev,
 | 
							dev_warn(&port->dev,
 | 
				
			||||||
			 "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
 | 
								 "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
 | 
				
			||||||
			 port->id, cxld->id, ctrl);
 | 
								 port->id, cxld->id, ctrl);
 | 
				
			||||||
		return -ENXIO;
 | 
							return rc;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cxld->interleave_granularity = to_interleave_granularity(ctrl);
 | 
						rc = cxl_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
 | 
				
			||||||
 | 
									&cxld->interleave_granularity);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (FIELD_GET(CXL_HDM_DECODER0_CTRL_TYPE, ctrl))
 | 
						if (!cxled) {
 | 
				
			||||||
		cxld->target_type = CXL_DECODER_EXPANDER;
 | 
							target_list.value =
 | 
				
			||||||
	else
 | 
								ioread64_hi_lo(hdm + CXL_HDM_DECODER0_TL_LOW(which));
 | 
				
			||||||
		cxld->target_type = CXL_DECODER_ACCELERATOR;
 | 
							for (i = 0; i < cxld->interleave_ways; i++)
 | 
				
			||||||
 | 
								target_map[i] = target_list.target_id[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (is_endpoint_decoder(&cxld->dev))
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!committed)
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	target_list.value =
 | 
						dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
 | 
				
			||||||
		ioread64_hi_lo(hdm + CXL_HDM_DECODER0_TL_LOW(which));
 | 
						if (remainder) {
 | 
				
			||||||
	for (i = 0; i < cxld->interleave_ways; i++)
 | 
							dev_err(&port->dev,
 | 
				
			||||||
		target_map[i] = target_list.target_id[i];
 | 
								"decoder%d.%d: invalid committed configuration size: %#llx ways: %d\n",
 | 
				
			||||||
 | 
								port->id, cxld->id, size, cxld->interleave_ways);
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						skip = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_SKIP_LOW(which));
 | 
				
			||||||
 | 
						rc = devm_cxl_dpa_reserve(cxled, *dpa_base + skip, dpa_size, skip);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							dev_err(&port->dev,
 | 
				
			||||||
 | 
								"decoder%d.%d: Failed to reserve DPA range %#llx - %#llx\n (%d)",
 | 
				
			||||||
 | 
								port->id, cxld->id, *dpa_base,
 | 
				
			||||||
 | 
								*dpa_base + dpa_size + skip - 1, rc);
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*dpa_base += dpa_size + skip;
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -216,7 +789,8 @@ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	void __iomem *hdm = cxlhdm->regs.hdm_decoder;
 | 
						void __iomem *hdm = cxlhdm->regs.hdm_decoder;
 | 
				
			||||||
	struct cxl_port *port = cxlhdm->port;
 | 
						struct cxl_port *port = cxlhdm->port;
 | 
				
			||||||
	int i, committed, failed;
 | 
						int i, committed;
 | 
				
			||||||
 | 
						u64 dpa_base = 0;
 | 
				
			||||||
	u32 ctrl;
 | 
						u32 ctrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
| 
						 | 
					@ -236,27 +810,37 @@ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
				
			||||||
	if (committed != cxlhdm->decoder_count)
 | 
						if (committed != cxlhdm->decoder_count)
 | 
				
			||||||
		msleep(20);
 | 
							msleep(20);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (i = 0, failed = 0; i < cxlhdm->decoder_count; i++) {
 | 
						for (i = 0; i < cxlhdm->decoder_count; i++) {
 | 
				
			||||||
		int target_map[CXL_DECODER_MAX_INTERLEAVE] = { 0 };
 | 
							int target_map[CXL_DECODER_MAX_INTERLEAVE] = { 0 };
 | 
				
			||||||
		int rc, target_count = cxlhdm->target_count;
 | 
							int rc, target_count = cxlhdm->target_count;
 | 
				
			||||||
		struct cxl_decoder *cxld;
 | 
							struct cxl_decoder *cxld;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (is_cxl_endpoint(port))
 | 
							if (is_cxl_endpoint(port)) {
 | 
				
			||||||
			cxld = cxl_endpoint_decoder_alloc(port);
 | 
								struct cxl_endpoint_decoder *cxled;
 | 
				
			||||||
		else
 | 
					
 | 
				
			||||||
			cxld = cxl_switch_decoder_alloc(port, target_count);
 | 
								cxled = cxl_endpoint_decoder_alloc(port);
 | 
				
			||||||
		if (IS_ERR(cxld)) {
 | 
								if (IS_ERR(cxled)) {
 | 
				
			||||||
			dev_warn(&port->dev,
 | 
									dev_warn(&port->dev,
 | 
				
			||||||
				 "Failed to allocate the decoder\n");
 | 
										 "Failed to allocate the decoder\n");
 | 
				
			||||||
			return PTR_ERR(cxld);
 | 
									return PTR_ERR(cxled);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cxld = &cxled->cxld;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								struct cxl_switch_decoder *cxlsd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								cxlsd = cxl_switch_decoder_alloc(port, target_count);
 | 
				
			||||||
 | 
								if (IS_ERR(cxlsd)) {
 | 
				
			||||||
 | 
									dev_warn(&port->dev,
 | 
				
			||||||
 | 
										 "Failed to allocate the decoder\n");
 | 
				
			||||||
 | 
									return PTR_ERR(cxlsd);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cxld = &cxlsd->cxld;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rc = init_hdm_decoder(port, cxld, target_map,
 | 
							rc = init_hdm_decoder(port, cxld, target_map, hdm, i, &dpa_base);
 | 
				
			||||||
				      cxlhdm->regs.hdm_decoder, i);
 | 
					 | 
				
			||||||
		if (rc) {
 | 
							if (rc) {
 | 
				
			||||||
			put_device(&cxld->dev);
 | 
								put_device(&cxld->dev);
 | 
				
			||||||
			failed++;
 | 
								return rc;
 | 
				
			||||||
			continue;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		rc = add_hdm_decoder(port, cxld, target_map);
 | 
							rc = add_hdm_decoder(port, cxld, target_map);
 | 
				
			||||||
		if (rc) {
 | 
							if (rc) {
 | 
				
			||||||
| 
						 | 
					@ -266,11 +850,6 @@ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (failed == cxlhdm->decoder_count) {
 | 
					 | 
				
			||||||
		dev_err(&port->dev, "No valid decoders found\n");
 | 
					 | 
				
			||||||
		return -ENXIO;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_decoders, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_decoders, CXL);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -718,12 +718,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_enumerate_cmds, CXL);
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
static int cxl_mem_get_partition_info(struct cxl_dev_state *cxlds)
 | 
					static int cxl_mem_get_partition_info(struct cxl_dev_state *cxlds)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_mbox_get_partition_info {
 | 
						struct cxl_mbox_get_partition_info pi;
 | 
				
			||||||
		__le64 active_volatile_cap;
 | 
					 | 
				
			||||||
		__le64 active_persistent_cap;
 | 
					 | 
				
			||||||
		__le64 next_volatile_cap;
 | 
					 | 
				
			||||||
		__le64 next_persistent_cap;
 | 
					 | 
				
			||||||
	} __packed pi;
 | 
					 | 
				
			||||||
	int rc;
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = cxl_mbox_send_cmd(cxlds, CXL_MBOX_OP_GET_PARTITION_INFO, NULL, 0,
 | 
						rc = cxl_mbox_send_cmd(cxlds, CXL_MBOX_OP_GET_PARTITION_INFO, NULL, 0,
 | 
				
			||||||
| 
						 | 
					@ -773,15 +768,6 @@ int cxl_dev_state_identify(struct cxl_dev_state *cxlds)
 | 
				
			||||||
	cxlds->partition_align_bytes =
 | 
						cxlds->partition_align_bytes =
 | 
				
			||||||
		le64_to_cpu(id.partition_align) * CXL_CAPACITY_MULTIPLIER;
 | 
							le64_to_cpu(id.partition_align) * CXL_CAPACITY_MULTIPLIER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dev_dbg(cxlds->dev,
 | 
					 | 
				
			||||||
		"Identify Memory Device\n"
 | 
					 | 
				
			||||||
		"     total_bytes = %#llx\n"
 | 
					 | 
				
			||||||
		"     volatile_only_bytes = %#llx\n"
 | 
					 | 
				
			||||||
		"     persistent_only_bytes = %#llx\n"
 | 
					 | 
				
			||||||
		"     partition_align_bytes = %#llx\n",
 | 
					 | 
				
			||||||
		cxlds->total_bytes, cxlds->volatile_only_bytes,
 | 
					 | 
				
			||||||
		cxlds->persistent_only_bytes, cxlds->partition_align_bytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cxlds->lsa_size = le32_to_cpu(id.lsa_size);
 | 
						cxlds->lsa_size = le32_to_cpu(id.lsa_size);
 | 
				
			||||||
	memcpy(cxlds->firmware_version, id.fw_revision, sizeof(id.fw_revision));
 | 
						memcpy(cxlds->firmware_version, id.fw_revision, sizeof(id.fw_revision));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -789,42 +775,63 @@ int cxl_dev_state_identify(struct cxl_dev_state *cxlds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(cxl_dev_state_identify, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(cxl_dev_state_identify, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int cxl_mem_create_range_info(struct cxl_dev_state *cxlds)
 | 
					static int add_dpa_res(struct device *dev, struct resource *parent,
 | 
				
			||||||
 | 
							       struct resource *res, resource_size_t start,
 | 
				
			||||||
 | 
							       resource_size_t size, const char *type)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int rc;
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (cxlds->partition_align_bytes == 0) {
 | 
						res->name = type;
 | 
				
			||||||
		cxlds->ram_range.start = 0;
 | 
						res->start = start;
 | 
				
			||||||
		cxlds->ram_range.end = cxlds->volatile_only_bytes - 1;
 | 
						res->end = start + size - 1;
 | 
				
			||||||
		cxlds->pmem_range.start = cxlds->volatile_only_bytes;
 | 
						res->flags = IORESOURCE_MEM;
 | 
				
			||||||
		cxlds->pmem_range.end = cxlds->volatile_only_bytes +
 | 
						if (resource_size(res) == 0) {
 | 
				
			||||||
				       cxlds->persistent_only_bytes - 1;
 | 
							dev_dbg(dev, "DPA(%s): no capacity\n", res->name);
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						rc = request_resource(parent, res);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							dev_err(dev, "DPA(%s): failed to track %pr (%d)\n", res->name,
 | 
				
			||||||
 | 
								res, rc);
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(dev, "DPA(%s): %pr\n", res->name, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int cxl_mem_create_range_info(struct cxl_dev_state *cxlds)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = cxlds->dev;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlds->dpa_res =
 | 
				
			||||||
 | 
							(struct resource)DEFINE_RES_MEM(0, cxlds->total_bytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxlds->partition_align_bytes == 0) {
 | 
				
			||||||
 | 
							rc = add_dpa_res(dev, &cxlds->dpa_res, &cxlds->ram_res, 0,
 | 
				
			||||||
 | 
									 cxlds->volatile_only_bytes, "ram");
 | 
				
			||||||
 | 
							if (rc)
 | 
				
			||||||
 | 
								return rc;
 | 
				
			||||||
 | 
							return add_dpa_res(dev, &cxlds->dpa_res, &cxlds->pmem_res,
 | 
				
			||||||
 | 
									   cxlds->volatile_only_bytes,
 | 
				
			||||||
 | 
									   cxlds->persistent_only_bytes, "pmem");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = cxl_mem_get_partition_info(cxlds);
 | 
						rc = cxl_mem_get_partition_info(cxlds);
 | 
				
			||||||
	if (rc) {
 | 
						if (rc) {
 | 
				
			||||||
		dev_err(cxlds->dev, "Failed to query partition information\n");
 | 
							dev_err(dev, "Failed to query partition information\n");
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dev_dbg(cxlds->dev,
 | 
						rc = add_dpa_res(dev, &cxlds->dpa_res, &cxlds->ram_res, 0,
 | 
				
			||||||
		"Get Partition Info\n"
 | 
								 cxlds->active_volatile_bytes, "ram");
 | 
				
			||||||
		"     active_volatile_bytes = %#llx\n"
 | 
						if (rc)
 | 
				
			||||||
		"     active_persistent_bytes = %#llx\n"
 | 
							return rc;
 | 
				
			||||||
		"     next_volatile_bytes = %#llx\n"
 | 
						return add_dpa_res(dev, &cxlds->dpa_res, &cxlds->pmem_res,
 | 
				
			||||||
		"     next_persistent_bytes = %#llx\n",
 | 
								   cxlds->active_volatile_bytes,
 | 
				
			||||||
		cxlds->active_volatile_bytes, cxlds->active_persistent_bytes,
 | 
								   cxlds->active_persistent_bytes, "pmem");
 | 
				
			||||||
		cxlds->next_volatile_bytes, cxlds->next_persistent_bytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cxlds->ram_range.start = 0;
 | 
					 | 
				
			||||||
	cxlds->ram_range.end = cxlds->active_volatile_bytes - 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cxlds->pmem_range.start = cxlds->active_volatile_bytes;
 | 
					 | 
				
			||||||
	cxlds->pmem_range.end =
 | 
					 | 
				
			||||||
		cxlds->active_volatile_bytes + cxlds->active_persistent_bytes - 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return 0;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(cxl_mem_create_range_info, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(cxl_mem_create_range_info, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -845,19 +852,11 @@ struct cxl_dev_state *cxl_dev_state_create(struct device *dev)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(cxl_dev_state_create, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(cxl_dev_state_create, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct dentry *cxl_debugfs;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void __init cxl_mbox_init(void)
 | 
					void __init cxl_mbox_init(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct dentry *mbox_debugfs;
 | 
						struct dentry *mbox_debugfs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxl_debugfs = debugfs_create_dir("cxl", NULL);
 | 
						mbox_debugfs = cxl_debugfs_create_dir("mbox");
 | 
				
			||||||
	mbox_debugfs = debugfs_create_dir("mbox", cxl_debugfs);
 | 
					 | 
				
			||||||
	debugfs_create_bool("raw_allow_all", 0600, mbox_debugfs,
 | 
						debugfs_create_bool("raw_allow_all", 0600, mbox_debugfs,
 | 
				
			||||||
			    &cxl_raw_allow_all);
 | 
								    &cxl_raw_allow_all);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
void cxl_mbox_exit(void)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	debugfs_remove_recursive(cxl_debugfs);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +68,7 @@ static ssize_t ram_size_show(struct device *dev, struct device_attribute *attr,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
						struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
				
			||||||
	struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
	unsigned long long len = range_len(&cxlds->ram_range);
 | 
						unsigned long long len = resource_size(&cxlds->ram_res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sysfs_emit(buf, "%#llx\n", len);
 | 
						return sysfs_emit(buf, "%#llx\n", len);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ static ssize_t pmem_size_show(struct device *dev, struct device_attribute *attr,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
						struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
				
			||||||
	struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
						struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
	unsigned long long len = range_len(&cxlds->pmem_range);
 | 
						unsigned long long len = resource_size(&cxlds->pmem_res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sysfs_emit(buf, "%#llx\n", len);
 | 
						return sysfs_emit(buf, "%#llx\n", len);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
#include <linux/device.h>
 | 
					#include <linux/device.h>
 | 
				
			||||||
#include <linux/delay.h>
 | 
					#include <linux/delay.h>
 | 
				
			||||||
#include <linux/pci.h>
 | 
					#include <linux/pci.h>
 | 
				
			||||||
 | 
					#include <linux/pci-doe.h>
 | 
				
			||||||
#include <cxlpci.h>
 | 
					#include <cxlpci.h>
 | 
				
			||||||
#include <cxlmem.h>
 | 
					#include <cxlmem.h>
 | 
				
			||||||
#include <cxl.h>
 | 
					#include <cxl.h>
 | 
				
			||||||
| 
						 | 
					@ -225,7 +226,6 @@ static int dvsec_range_allowed(struct device *dev, void *arg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct range *dev_range = arg;
 | 
						struct range *dev_range = arg;
 | 
				
			||||||
	struct cxl_decoder *cxld;
 | 
						struct cxl_decoder *cxld;
 | 
				
			||||||
	struct range root_range;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!is_root_decoder(dev))
 | 
						if (!is_root_decoder(dev))
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
| 
						 | 
					@ -237,12 +237,7 @@ static int dvsec_range_allowed(struct device *dev, void *arg)
 | 
				
			||||||
	if (!(cxld->flags & CXL_DECODER_F_RAM))
 | 
						if (!(cxld->flags & CXL_DECODER_F_RAM))
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	root_range = (struct range) {
 | 
						return range_contains(&cxld->hpa_range, dev_range);
 | 
				
			||||||
		.start = cxld->platform_res.start,
 | 
					 | 
				
			||||||
		.end = cxld->platform_res.end,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return range_contains(&root_range, dev_range);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void disable_hdm(void *_cxlhdm)
 | 
					static void disable_hdm(void *_cxlhdm)
 | 
				
			||||||
| 
						 | 
					@ -458,3 +453,175 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CXL_DOE_TABLE_ACCESS_REQ_CODE		0x000000ff
 | 
				
			||||||
 | 
					#define   CXL_DOE_TABLE_ACCESS_REQ_CODE_READ	0
 | 
				
			||||||
 | 
					#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE		0x0000ff00
 | 
				
			||||||
 | 
					#define   CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA	0
 | 
				
			||||||
 | 
					#define CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE	0xffff0000
 | 
				
			||||||
 | 
					#define CXL_DOE_TABLE_ACCESS_LAST_ENTRY		0xffff
 | 
				
			||||||
 | 
					#define CXL_DOE_PROTOCOL_TABLE_ACCESS 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct pci_doe_mb *find_cdat_doe(struct device *uport)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd;
 | 
				
			||||||
 | 
						struct cxl_dev_state *cxlds;
 | 
				
			||||||
 | 
						unsigned long index;
 | 
				
			||||||
 | 
						void *entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlmd = to_cxl_memdev(uport);
 | 
				
			||||||
 | 
						cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xa_for_each(&cxlds->doe_mbs, index, entry) {
 | 
				
			||||||
 | 
							struct pci_doe_mb *cur = entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (pci_doe_supports_prot(cur, PCI_DVSEC_VENDOR_ID_CXL,
 | 
				
			||||||
 | 
										  CXL_DOE_PROTOCOL_TABLE_ACCESS))
 | 
				
			||||||
 | 
								return cur;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CDAT_DOE_REQ(entry_handle)					\
 | 
				
			||||||
 | 
						(FIELD_PREP(CXL_DOE_TABLE_ACCESS_REQ_CODE,			\
 | 
				
			||||||
 | 
							    CXL_DOE_TABLE_ACCESS_REQ_CODE_READ) |		\
 | 
				
			||||||
 | 
						 FIELD_PREP(CXL_DOE_TABLE_ACCESS_TABLE_TYPE,			\
 | 
				
			||||||
 | 
							    CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA) |		\
 | 
				
			||||||
 | 
						 FIELD_PREP(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, (entry_handle)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxl_doe_task_complete(struct pci_doe_task *task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						complete(task->private);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct cdat_doe_task {
 | 
				
			||||||
 | 
						u32 request_pl;
 | 
				
			||||||
 | 
						u32 response_pl[32];
 | 
				
			||||||
 | 
						struct completion c;
 | 
				
			||||||
 | 
						struct pci_doe_task task;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DECLARE_CDAT_DOE_TASK(req, cdt)                       \
 | 
				
			||||||
 | 
					struct cdat_doe_task cdt = {                                  \
 | 
				
			||||||
 | 
						.c = COMPLETION_INITIALIZER_ONSTACK(cdt.c),           \
 | 
				
			||||||
 | 
						.request_pl = req,				      \
 | 
				
			||||||
 | 
						.task = {                                             \
 | 
				
			||||||
 | 
							.prot.vid = PCI_DVSEC_VENDOR_ID_CXL,        \
 | 
				
			||||||
 | 
							.prot.type = CXL_DOE_PROTOCOL_TABLE_ACCESS, \
 | 
				
			||||||
 | 
							.request_pl = &cdt.request_pl,                \
 | 
				
			||||||
 | 
							.request_pl_sz = sizeof(cdt.request_pl),      \
 | 
				
			||||||
 | 
							.response_pl = cdt.response_pl,               \
 | 
				
			||||||
 | 
							.response_pl_sz = sizeof(cdt.response_pl),    \
 | 
				
			||||||
 | 
							.complete = cxl_doe_task_complete,            \
 | 
				
			||||||
 | 
							.private = &cdt.c,                            \
 | 
				
			||||||
 | 
						}                                                     \
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_cdat_get_length(struct device *dev,
 | 
				
			||||||
 | 
								       struct pci_doe_mb *cdat_doe,
 | 
				
			||||||
 | 
								       size_t *length)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(0), t);
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = pci_doe_submit_task(cdat_doe, &t.task);
 | 
				
			||||||
 | 
						if (rc < 0) {
 | 
				
			||||||
 | 
							dev_err(dev, "DOE submit failed: %d", rc);
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						wait_for_completion(&t.c);
 | 
				
			||||||
 | 
						if (t.task.rv < sizeof(u32))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*length = t.response_pl[1];
 | 
				
			||||||
 | 
						dev_dbg(dev, "CDAT length %zu\n", *length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_cdat_read_table(struct device *dev,
 | 
				
			||||||
 | 
								       struct pci_doe_mb *cdat_doe,
 | 
				
			||||||
 | 
								       struct cxl_cdat *cdat)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						size_t length = cdat->length;
 | 
				
			||||||
 | 
						u32 *data = cdat->table;
 | 
				
			||||||
 | 
						int entry_handle = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do {
 | 
				
			||||||
 | 
							DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(entry_handle), t);
 | 
				
			||||||
 | 
							size_t entry_dw;
 | 
				
			||||||
 | 
							u32 *entry;
 | 
				
			||||||
 | 
							int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rc = pci_doe_submit_task(cdat_doe, &t.task);
 | 
				
			||||||
 | 
							if (rc < 0) {
 | 
				
			||||||
 | 
								dev_err(dev, "DOE submit failed: %d", rc);
 | 
				
			||||||
 | 
								return rc;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wait_for_completion(&t.c);
 | 
				
			||||||
 | 
							/* 1 DW header + 1 DW data min */
 | 
				
			||||||
 | 
							if (t.task.rv < (2 * sizeof(u32)))
 | 
				
			||||||
 | 
								return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Get the CXL table access header entry handle */
 | 
				
			||||||
 | 
							entry_handle = FIELD_GET(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE,
 | 
				
			||||||
 | 
										 t.response_pl[0]);
 | 
				
			||||||
 | 
							entry = t.response_pl + 1;
 | 
				
			||||||
 | 
							entry_dw = t.task.rv / sizeof(u32);
 | 
				
			||||||
 | 
							/* Skip Header */
 | 
				
			||||||
 | 
							entry_dw -= 1;
 | 
				
			||||||
 | 
							entry_dw = min(length / sizeof(u32), entry_dw);
 | 
				
			||||||
 | 
							/* Prevent length < 1 DW from causing a buffer overflow */
 | 
				
			||||||
 | 
							if (entry_dw) {
 | 
				
			||||||
 | 
								memcpy(data, entry, entry_dw * sizeof(u32));
 | 
				
			||||||
 | 
								length -= entry_dw * sizeof(u32);
 | 
				
			||||||
 | 
								data += entry_dw;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} while (entry_handle != CXL_DOE_TABLE_ACCESS_LAST_ENTRY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * read_cdat_data - Read the CDAT data on this port
 | 
				
			||||||
 | 
					 * @port: Port to read data from
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This call will sleep waiting for responses from the DOE mailbox.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void read_cdat_data(struct cxl_port *port)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *cdat_doe;
 | 
				
			||||||
 | 
						struct device *dev = &port->dev;
 | 
				
			||||||
 | 
						struct device *uport = port->uport;
 | 
				
			||||||
 | 
						size_t cdat_length;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cdat_doe = find_cdat_doe(uport);
 | 
				
			||||||
 | 
						if (!cdat_doe) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "No CDAT mailbox\n");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->cdat_available = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxl_cdat_get_length(dev, cdat_doe, &cdat_length)) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "No CDAT length\n");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->cdat.table = devm_kzalloc(dev, cdat_length, GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!port->cdat.table)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->cdat.length = cdat_length;
 | 
				
			||||||
 | 
						rc = cxl_cdat_read_table(dev, cdat_doe, &port->cdat);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							/* Don't leave table data allocated on error */
 | 
				
			||||||
 | 
							devm_kfree(dev, port->cdat.table);
 | 
				
			||||||
 | 
							port->cdat.table = NULL;
 | 
				
			||||||
 | 
							port->cdat.length = 0;
 | 
				
			||||||
 | 
							dev_err(dev, "CDAT data read error\n");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_NS_GPL(read_cdat_data, CXL);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,9 +62,9 @@ static int match_nvdimm_bridge(struct device *dev, void *data)
 | 
				
			||||||
	return is_cxl_nvdimm_bridge(dev);
 | 
						return is_cxl_nvdimm_bridge(dev);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd)
 | 
					struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct device *start)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_port *port = find_cxl_root(&cxl_nvd->dev);
 | 
						struct cxl_port *port = find_cxl_root(start);
 | 
				
			||||||
	struct device *dev;
 | 
						struct device *dev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!port)
 | 
						if (!port)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										1896
									
								
								drivers/cxl/core/region.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1896
									
								
								drivers/cxl/core/region.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -7,6 +7,7 @@
 | 
				
			||||||
#include <linux/libnvdimm.h>
 | 
					#include <linux/libnvdimm.h>
 | 
				
			||||||
#include <linux/bitfield.h>
 | 
					#include <linux/bitfield.h>
 | 
				
			||||||
#include <linux/bitops.h>
 | 
					#include <linux/bitops.h>
 | 
				
			||||||
 | 
					#include <linux/log2.h>
 | 
				
			||||||
#include <linux/io.h>
 | 
					#include <linux/io.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -53,9 +54,12 @@
 | 
				
			||||||
#define   CXL_HDM_DECODER0_CTRL_LOCK BIT(8)
 | 
					#define   CXL_HDM_DECODER0_CTRL_LOCK BIT(8)
 | 
				
			||||||
#define   CXL_HDM_DECODER0_CTRL_COMMIT BIT(9)
 | 
					#define   CXL_HDM_DECODER0_CTRL_COMMIT BIT(9)
 | 
				
			||||||
#define   CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
 | 
					#define   CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
 | 
				
			||||||
 | 
					#define   CXL_HDM_DECODER0_CTRL_COMMIT_ERROR BIT(11)
 | 
				
			||||||
#define   CXL_HDM_DECODER0_CTRL_TYPE BIT(12)
 | 
					#define   CXL_HDM_DECODER0_CTRL_TYPE BIT(12)
 | 
				
			||||||
#define CXL_HDM_DECODER0_TL_LOW(i) (0x20 * (i) + 0x24)
 | 
					#define CXL_HDM_DECODER0_TL_LOW(i) (0x20 * (i) + 0x24)
 | 
				
			||||||
#define CXL_HDM_DECODER0_TL_HIGH(i) (0x20 * (i) + 0x28)
 | 
					#define CXL_HDM_DECODER0_TL_HIGH(i) (0x20 * (i) + 0x28)
 | 
				
			||||||
 | 
					#define CXL_HDM_DECODER0_SKIP_LOW(i) CXL_HDM_DECODER0_TL_LOW(i)
 | 
				
			||||||
 | 
					#define CXL_HDM_DECODER0_SKIP_HIGH(i) CXL_HDM_DECODER0_TL_HIGH(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline int cxl_hdm_decoder_count(u32 cap_hdr)
 | 
					static inline int cxl_hdm_decoder_count(u32 cap_hdr)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -64,6 +68,57 @@ static inline int cxl_hdm_decoder_count(u32 cap_hdr)
 | 
				
			||||||
	return val ? val * 2 : 1;
 | 
						return val ? val * 2 : 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Encode defined in CXL 2.0 8.2.5.12.7 HDM Decoder Control Register */
 | 
				
			||||||
 | 
					static inline int cxl_to_granularity(u16 ig, unsigned int *val)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (ig > 6)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						*val = 256 << ig;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Encode defined in CXL ECN "3, 6, 12 and 16-way memory Interleaving" */
 | 
				
			||||||
 | 
					static inline int cxl_to_ways(u8 eniw, unsigned int *val)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						switch (eniw) {
 | 
				
			||||||
 | 
						case 0 ... 4:
 | 
				
			||||||
 | 
							*val = 1 << eniw;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case 8 ... 10:
 | 
				
			||||||
 | 
							*val = 3 << (eniw - 8);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline int granularity_to_cxl(int g, u16 *ig)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (g > SZ_16K || g < 256 || !is_power_of_2(g))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						*ig = ilog2(g) - 8;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline int ways_to_cxl(unsigned int ways, u8 *iw)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (ways > 16)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						if (is_power_of_2(ways)) {
 | 
				
			||||||
 | 
							*iw = ilog2(ways);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (ways % 3)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						ways /= 3;
 | 
				
			||||||
 | 
						if (!is_power_of_2(ways))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						*iw = ilog2(ways) + 8;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
 | 
					/* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
 | 
				
			||||||
#define CXLDEV_CAP_ARRAY_OFFSET 0x0
 | 
					#define CXLDEV_CAP_ARRAY_OFFSET 0x0
 | 
				
			||||||
#define   CXLDEV_CAP_ARRAY_CAP_ID 0
 | 
					#define   CXLDEV_CAP_ARRAY_CAP_ID 0
 | 
				
			||||||
| 
						 | 
					@ -193,37 +248,153 @@ enum cxl_decoder_type {
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
#define CXL_DECODER_MAX_INTERLEAVE 16
 | 
					#define CXL_DECODER_MAX_INTERLEAVE 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CXL_DECODER_MIN_GRANULARITY 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * struct cxl_decoder - CXL address range decode configuration
 | 
					 * struct cxl_decoder - Common CXL HDM Decoder Attributes
 | 
				
			||||||
 * @dev: this decoder's device
 | 
					 * @dev: this decoder's device
 | 
				
			||||||
 * @id: kernel device name id
 | 
					 * @id: kernel device name id
 | 
				
			||||||
 * @platform_res: address space resources considered by root decoder
 | 
					 * @hpa_range: Host physical address range mapped by this decoder
 | 
				
			||||||
 * @decoder_range: address space resources considered by midlevel decoder
 | 
					 | 
				
			||||||
 * @interleave_ways: number of cxl_dports in this decode
 | 
					 * @interleave_ways: number of cxl_dports in this decode
 | 
				
			||||||
 * @interleave_granularity: data stride per dport
 | 
					 * @interleave_granularity: data stride per dport
 | 
				
			||||||
 * @target_type: accelerator vs expander (type2 vs type3) selector
 | 
					 * @target_type: accelerator vs expander (type2 vs type3) selector
 | 
				
			||||||
 | 
					 * @region: currently assigned region for this decoder
 | 
				
			||||||
 * @flags: memory type capabilities and locking
 | 
					 * @flags: memory type capabilities and locking
 | 
				
			||||||
 * @target_lock: coordinate coherent reads of the target list
 | 
					 * @commit: device/decoder-type specific callback to commit settings to hw
 | 
				
			||||||
 * @nr_targets: number of elements in @target
 | 
					 * @reset: device/decoder-type specific callback to reset hw settings
 | 
				
			||||||
 * @target: active ordered target list in current decoder configuration
 | 
					*/
 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
struct cxl_decoder {
 | 
					struct cxl_decoder {
 | 
				
			||||||
	struct device dev;
 | 
						struct device dev;
 | 
				
			||||||
	int id;
 | 
						int id;
 | 
				
			||||||
	union {
 | 
						struct range hpa_range;
 | 
				
			||||||
		struct resource platform_res;
 | 
					 | 
				
			||||||
		struct range decoder_range;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	int interleave_ways;
 | 
						int interleave_ways;
 | 
				
			||||||
	int interleave_granularity;
 | 
						int interleave_granularity;
 | 
				
			||||||
	enum cxl_decoder_type target_type;
 | 
						enum cxl_decoder_type target_type;
 | 
				
			||||||
 | 
						struct cxl_region *region;
 | 
				
			||||||
	unsigned long flags;
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						int (*commit)(struct cxl_decoder *cxld);
 | 
				
			||||||
 | 
						int (*reset)(struct cxl_decoder *cxld);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * CXL_DECODER_DEAD prevents endpoints from being reattached to regions
 | 
				
			||||||
 | 
					 * while cxld_unregister() is running
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum cxl_decoder_mode {
 | 
				
			||||||
 | 
						CXL_DECODER_NONE,
 | 
				
			||||||
 | 
						CXL_DECODER_RAM,
 | 
				
			||||||
 | 
						CXL_DECODER_PMEM,
 | 
				
			||||||
 | 
						CXL_DECODER_MIXED,
 | 
				
			||||||
 | 
						CXL_DECODER_DEAD,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_endpoint_decoder - Endpoint  / SPA to DPA decoder
 | 
				
			||||||
 | 
					 * @cxld: base cxl_decoder_object
 | 
				
			||||||
 | 
					 * @dpa_res: actively claimed DPA span of this decoder
 | 
				
			||||||
 | 
					 * @skip: offset into @dpa_res where @cxld.hpa_range maps
 | 
				
			||||||
 | 
					 * @mode: which memory type / access-mode-partition this decoder targets
 | 
				
			||||||
 | 
					 * @pos: interleave position in @cxld.region
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_endpoint_decoder {
 | 
				
			||||||
 | 
						struct cxl_decoder cxld;
 | 
				
			||||||
 | 
						struct resource *dpa_res;
 | 
				
			||||||
 | 
						resource_size_t skip;
 | 
				
			||||||
 | 
						enum cxl_decoder_mode mode;
 | 
				
			||||||
 | 
						int pos;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_switch_decoder - Switch specific CXL HDM Decoder
 | 
				
			||||||
 | 
					 * @cxld: base cxl_decoder object
 | 
				
			||||||
 | 
					 * @target_lock: coordinate coherent reads of the target list
 | 
				
			||||||
 | 
					 * @nr_targets: number of elements in @target
 | 
				
			||||||
 | 
					 * @target: active ordered target list in current decoder configuration
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The 'switch' decoder type represents the decoder instances of cxl_port's that
 | 
				
			||||||
 | 
					 * route from the root of a CXL memory decode topology to the endpoints. They
 | 
				
			||||||
 | 
					 * come in two flavors, root-level decoders, statically defined by platform
 | 
				
			||||||
 | 
					 * firmware, and mid-level decoders, where interleave-granularity,
 | 
				
			||||||
 | 
					 * interleave-width, and the target list are mutable.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_switch_decoder {
 | 
				
			||||||
 | 
						struct cxl_decoder cxld;
 | 
				
			||||||
	seqlock_t target_lock;
 | 
						seqlock_t target_lock;
 | 
				
			||||||
	int nr_targets;
 | 
						int nr_targets;
 | 
				
			||||||
	struct cxl_dport *target[];
 | 
						struct cxl_dport *target[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_root_decoder - Static platform CXL address decoder
 | 
				
			||||||
 | 
					 * @res: host / parent resource for region allocations
 | 
				
			||||||
 | 
					 * @region_id: region id for next region provisioning event
 | 
				
			||||||
 | 
					 * @calc_hb: which host bridge covers the n'th position by granularity
 | 
				
			||||||
 | 
					 * @cxlsd: base cxl switch decoder
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_root_decoder {
 | 
				
			||||||
 | 
						struct resource *res;
 | 
				
			||||||
 | 
						atomic_t region_id;
 | 
				
			||||||
 | 
						struct cxl_dport *(*calc_hb)(struct cxl_root_decoder *cxlrd, int pos);
 | 
				
			||||||
 | 
						struct cxl_switch_decoder cxlsd;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * enum cxl_config_state - State machine for region configuration
 | 
				
			||||||
 | 
					 * @CXL_CONFIG_IDLE: Any sysfs attribute can be written freely
 | 
				
			||||||
 | 
					 * @CXL_CONFIG_INTERLEAVE_ACTIVE: region size has been set, no more
 | 
				
			||||||
 | 
					 * changes to interleave_ways or interleave_granularity
 | 
				
			||||||
 | 
					 * @CXL_CONFIG_ACTIVE: All targets have been added the region is now
 | 
				
			||||||
 | 
					 * active
 | 
				
			||||||
 | 
					 * @CXL_CONFIG_RESET_PENDING: see commit_store()
 | 
				
			||||||
 | 
					 * @CXL_CONFIG_COMMIT: Soft-config has been committed to hardware
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum cxl_config_state {
 | 
				
			||||||
 | 
						CXL_CONFIG_IDLE,
 | 
				
			||||||
 | 
						CXL_CONFIG_INTERLEAVE_ACTIVE,
 | 
				
			||||||
 | 
						CXL_CONFIG_ACTIVE,
 | 
				
			||||||
 | 
						CXL_CONFIG_RESET_PENDING,
 | 
				
			||||||
 | 
						CXL_CONFIG_COMMIT,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_region_params - region settings
 | 
				
			||||||
 | 
					 * @state: allow the driver to lockdown further parameter changes
 | 
				
			||||||
 | 
					 * @uuid: unique id for persistent regions
 | 
				
			||||||
 | 
					 * @interleave_ways: number of endpoints in the region
 | 
				
			||||||
 | 
					 * @interleave_granularity: capacity each endpoint contributes to a stripe
 | 
				
			||||||
 | 
					 * @res: allocated iomem capacity for this region
 | 
				
			||||||
 | 
					 * @targets: active ordered targets in current decoder configuration
 | 
				
			||||||
 | 
					 * @nr_targets: number of targets
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * State transitions are protected by the cxl_region_rwsem
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_region_params {
 | 
				
			||||||
 | 
						enum cxl_config_state state;
 | 
				
			||||||
 | 
						uuid_t uuid;
 | 
				
			||||||
 | 
						int interleave_ways;
 | 
				
			||||||
 | 
						int interleave_granularity;
 | 
				
			||||||
 | 
						struct resource *res;
 | 
				
			||||||
 | 
						struct cxl_endpoint_decoder *targets[CXL_DECODER_MAX_INTERLEAVE];
 | 
				
			||||||
 | 
						int nr_targets;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_region - CXL region
 | 
				
			||||||
 | 
					 * @dev: This region's device
 | 
				
			||||||
 | 
					 * @id: This region's id. Id is globally unique across all regions
 | 
				
			||||||
 | 
					 * @mode: Endpoint decoder allocation / access mode
 | 
				
			||||||
 | 
					 * @type: Endpoint decoder target type
 | 
				
			||||||
 | 
					 * @params: active + config params for the region
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_region {
 | 
				
			||||||
 | 
						struct device dev;
 | 
				
			||||||
 | 
						int id;
 | 
				
			||||||
 | 
						enum cxl_decoder_mode mode;
 | 
				
			||||||
 | 
						enum cxl_decoder_type type;
 | 
				
			||||||
 | 
						struct cxl_region_params params;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * enum cxl_nvdimm_brige_state - state machine for managing bus rescans
 | 
					 * enum cxl_nvdimm_brige_state - state machine for managing bus rescans
 | 
				
			||||||
 * @CXL_NVB_NEW: Set at bridge create and after cxl_pmem_wq is destroyed
 | 
					 * @CXL_NVB_NEW: Set at bridge create and after cxl_pmem_wq is destroyed
 | 
				
			||||||
| 
						 | 
					@ -251,7 +422,26 @@ struct cxl_nvdimm_bridge {
 | 
				
			||||||
struct cxl_nvdimm {
 | 
					struct cxl_nvdimm {
 | 
				
			||||||
	struct device dev;
 | 
						struct device dev;
 | 
				
			||||||
	struct cxl_memdev *cxlmd;
 | 
						struct cxl_memdev *cxlmd;
 | 
				
			||||||
	struct nvdimm *nvdimm;
 | 
						struct cxl_nvdimm_bridge *bridge;
 | 
				
			||||||
 | 
						struct cxl_pmem_region *region;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct cxl_pmem_region_mapping {
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd;
 | 
				
			||||||
 | 
						struct cxl_nvdimm *cxl_nvd;
 | 
				
			||||||
 | 
						u64 start;
 | 
				
			||||||
 | 
						u64 size;
 | 
				
			||||||
 | 
						int position;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct cxl_pmem_region {
 | 
				
			||||||
 | 
						struct device dev;
 | 
				
			||||||
 | 
						struct cxl_region *cxlr;
 | 
				
			||||||
 | 
						struct nd_region *nd_region;
 | 
				
			||||||
 | 
						struct cxl_nvdimm_bridge *bridge;
 | 
				
			||||||
 | 
						struct range hpa_range;
 | 
				
			||||||
 | 
						int nr_mappings;
 | 
				
			||||||
 | 
						struct cxl_pmem_region_mapping mapping[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -260,50 +450,94 @@ struct cxl_nvdimm {
 | 
				
			||||||
 *		     decode hierarchy.
 | 
					 *		     decode hierarchy.
 | 
				
			||||||
 * @dev: this port's device
 | 
					 * @dev: this port's device
 | 
				
			||||||
 * @uport: PCI or platform device implementing the upstream port capability
 | 
					 * @uport: PCI or platform device implementing the upstream port capability
 | 
				
			||||||
 | 
					 * @host_bridge: Shortcut to the platform attach point for this port
 | 
				
			||||||
 * @id: id for port device-name
 | 
					 * @id: id for port device-name
 | 
				
			||||||
 * @dports: cxl_dport instances referenced by decoders
 | 
					 * @dports: cxl_dport instances referenced by decoders
 | 
				
			||||||
 * @endpoints: cxl_ep instances, endpoints that are a descendant of this port
 | 
					 * @endpoints: cxl_ep instances, endpoints that are a descendant of this port
 | 
				
			||||||
 | 
					 * @regions: cxl_region_ref instances, regions mapped by this port
 | 
				
			||||||
 | 
					 * @parent_dport: dport that points to this port in the parent
 | 
				
			||||||
 * @decoder_ida: allocator for decoder ids
 | 
					 * @decoder_ida: allocator for decoder ids
 | 
				
			||||||
 | 
					 * @hdm_end: track last allocated HDM decoder instance for allocation ordering
 | 
				
			||||||
 | 
					 * @commit_end: cursor to track highest committed decoder for commit ordering
 | 
				
			||||||
 * @component_reg_phys: component register capability base address (optional)
 | 
					 * @component_reg_phys: component register capability base address (optional)
 | 
				
			||||||
 * @dead: last ep has been removed, force port re-creation
 | 
					 * @dead: last ep has been removed, force port re-creation
 | 
				
			||||||
 * @depth: How deep this port is relative to the root. depth 0 is the root.
 | 
					 * @depth: How deep this port is relative to the root. depth 0 is the root.
 | 
				
			||||||
 | 
					 * @cdat: Cached CDAT data
 | 
				
			||||||
 | 
					 * @cdat_available: Should a CDAT attribute be available in sysfs
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct cxl_port {
 | 
					struct cxl_port {
 | 
				
			||||||
	struct device dev;
 | 
						struct device dev;
 | 
				
			||||||
	struct device *uport;
 | 
						struct device *uport;
 | 
				
			||||||
 | 
						struct device *host_bridge;
 | 
				
			||||||
	int id;
 | 
						int id;
 | 
				
			||||||
	struct list_head dports;
 | 
						struct xarray dports;
 | 
				
			||||||
	struct list_head endpoints;
 | 
						struct xarray endpoints;
 | 
				
			||||||
 | 
						struct xarray regions;
 | 
				
			||||||
 | 
						struct cxl_dport *parent_dport;
 | 
				
			||||||
	struct ida decoder_ida;
 | 
						struct ida decoder_ida;
 | 
				
			||||||
 | 
						int hdm_end;
 | 
				
			||||||
 | 
						int commit_end;
 | 
				
			||||||
	resource_size_t component_reg_phys;
 | 
						resource_size_t component_reg_phys;
 | 
				
			||||||
	bool dead;
 | 
						bool dead;
 | 
				
			||||||
	unsigned int depth;
 | 
						unsigned int depth;
 | 
				
			||||||
 | 
						struct cxl_cdat {
 | 
				
			||||||
 | 
							void *table;
 | 
				
			||||||
 | 
							size_t length;
 | 
				
			||||||
 | 
						} cdat;
 | 
				
			||||||
 | 
						bool cdat_available;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct cxl_dport *
 | 
				
			||||||
 | 
					cxl_find_dport_by_dev(struct cxl_port *port, const struct device *dport_dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return xa_load(&port->dports, (unsigned long)dport_dev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * struct cxl_dport - CXL downstream port
 | 
					 * struct cxl_dport - CXL downstream port
 | 
				
			||||||
 * @dport: PCI bridge or firmware device representing the downstream link
 | 
					 * @dport: PCI bridge or firmware device representing the downstream link
 | 
				
			||||||
 * @port_id: unique hardware identifier for dport in decoder target list
 | 
					 * @port_id: unique hardware identifier for dport in decoder target list
 | 
				
			||||||
 * @component_reg_phys: downstream port component registers
 | 
					 * @component_reg_phys: downstream port component registers
 | 
				
			||||||
 * @port: reference to cxl_port that contains this downstream port
 | 
					 * @port: reference to cxl_port that contains this downstream port
 | 
				
			||||||
 * @list: node for a cxl_port's list of cxl_dport instances
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct cxl_dport {
 | 
					struct cxl_dport {
 | 
				
			||||||
	struct device *dport;
 | 
						struct device *dport;
 | 
				
			||||||
	int port_id;
 | 
						int port_id;
 | 
				
			||||||
	resource_size_t component_reg_phys;
 | 
						resource_size_t component_reg_phys;
 | 
				
			||||||
	struct cxl_port *port;
 | 
						struct cxl_port *port;
 | 
				
			||||||
	struct list_head list;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * struct cxl_ep - track an endpoint's interest in a port
 | 
					 * struct cxl_ep - track an endpoint's interest in a port
 | 
				
			||||||
 * @ep: device that hosts a generic CXL endpoint (expander or accelerator)
 | 
					 * @ep: device that hosts a generic CXL endpoint (expander or accelerator)
 | 
				
			||||||
 * @list: node on port->endpoints list
 | 
					 * @dport: which dport routes to this endpoint on @port
 | 
				
			||||||
 | 
					 * @next: cxl switch port across the link attached to @dport NULL if
 | 
				
			||||||
 | 
					 *	  attached to an endpoint
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct cxl_ep {
 | 
					struct cxl_ep {
 | 
				
			||||||
	struct device *ep;
 | 
						struct device *ep;
 | 
				
			||||||
	struct list_head list;
 | 
						struct cxl_dport *dport;
 | 
				
			||||||
 | 
						struct cxl_port *next;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct cxl_region_ref - track a region's interest in a port
 | 
				
			||||||
 | 
					 * @port: point in topology to install this reference
 | 
				
			||||||
 | 
					 * @decoder: decoder assigned for @region in @port
 | 
				
			||||||
 | 
					 * @region: region for this reference
 | 
				
			||||||
 | 
					 * @endpoints: cxl_ep references for region members beneath @port
 | 
				
			||||||
 | 
					 * @nr_targets_set: track how many targets have been programmed during setup
 | 
				
			||||||
 | 
					 * @nr_eps: number of endpoints beneath @port
 | 
				
			||||||
 | 
					 * @nr_targets: number of distinct targets needed to reach @nr_eps
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct cxl_region_ref {
 | 
				
			||||||
 | 
						struct cxl_port *port;
 | 
				
			||||||
 | 
						struct cxl_decoder *decoder;
 | 
				
			||||||
 | 
						struct cxl_region *region;
 | 
				
			||||||
 | 
						struct xarray endpoints;
 | 
				
			||||||
 | 
						int nr_targets_set;
 | 
				
			||||||
 | 
						int nr_eps;
 | 
				
			||||||
 | 
						int nr_targets;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -325,29 +559,31 @@ int devm_cxl_register_pci_bus(struct device *host, struct device *uport,
 | 
				
			||||||
struct pci_bus *cxl_port_to_pci_bus(struct cxl_port *port);
 | 
					struct pci_bus *cxl_port_to_pci_bus(struct cxl_port *port);
 | 
				
			||||||
struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
 | 
					struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
 | 
				
			||||||
				   resource_size_t component_reg_phys,
 | 
									   resource_size_t component_reg_phys,
 | 
				
			||||||
				   struct cxl_port *parent_port);
 | 
									   struct cxl_dport *parent_dport);
 | 
				
			||||||
 | 
					int devm_cxl_add_endpoint(struct cxl_memdev *cxlmd,
 | 
				
			||||||
 | 
								  struct cxl_dport *parent_dport);
 | 
				
			||||||
struct cxl_port *find_cxl_root(struct device *dev);
 | 
					struct cxl_port *find_cxl_root(struct device *dev);
 | 
				
			||||||
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
 | 
					int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
 | 
				
			||||||
int cxl_bus_rescan(void);
 | 
					int cxl_bus_rescan(void);
 | 
				
			||||||
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd);
 | 
					struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
 | 
				
			||||||
 | 
									   struct cxl_dport **dport);
 | 
				
			||||||
bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd);
 | 
					bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port,
 | 
					struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port,
 | 
				
			||||||
				     struct device *dport, int port_id,
 | 
									     struct device *dport, int port_id,
 | 
				
			||||||
				     resource_size_t component_reg_phys);
 | 
									     resource_size_t component_reg_phys);
 | 
				
			||||||
struct cxl_dport *cxl_find_dport_by_dev(struct cxl_port *port,
 | 
					 | 
				
			||||||
					const struct device *dev);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_decoder *to_cxl_decoder(struct device *dev);
 | 
					struct cxl_decoder *to_cxl_decoder(struct device *dev);
 | 
				
			||||||
 | 
					struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
 | 
				
			||||||
 | 
					struct cxl_endpoint_decoder *to_cxl_endpoint_decoder(struct device *dev);
 | 
				
			||||||
bool is_root_decoder(struct device *dev);
 | 
					bool is_root_decoder(struct device *dev);
 | 
				
			||||||
bool is_endpoint_decoder(struct device *dev);
 | 
					bool is_endpoint_decoder(struct device *dev);
 | 
				
			||||||
bool is_cxl_decoder(struct device *dev);
 | 
					struct cxl_root_decoder *cxl_root_decoder_alloc(struct cxl_port *port,
 | 
				
			||||||
struct cxl_decoder *cxl_root_decoder_alloc(struct cxl_port *port,
 | 
											unsigned int nr_targets);
 | 
				
			||||||
					   unsigned int nr_targets);
 | 
					struct cxl_switch_decoder *cxl_switch_decoder_alloc(struct cxl_port *port,
 | 
				
			||||||
struct cxl_decoder *cxl_switch_decoder_alloc(struct cxl_port *port,
 | 
											    unsigned int nr_targets);
 | 
				
			||||||
					     unsigned int nr_targets);
 | 
					 | 
				
			||||||
int cxl_decoder_add(struct cxl_decoder *cxld, int *target_map);
 | 
					int cxl_decoder_add(struct cxl_decoder *cxld, int *target_map);
 | 
				
			||||||
struct cxl_decoder *cxl_endpoint_decoder_alloc(struct cxl_port *port);
 | 
					struct cxl_endpoint_decoder *cxl_endpoint_decoder_alloc(struct cxl_port *port);
 | 
				
			||||||
int cxl_decoder_add_locked(struct cxl_decoder *cxld, int *target_map);
 | 
					int cxl_decoder_add_locked(struct cxl_decoder *cxld, int *target_map);
 | 
				
			||||||
int cxl_decoder_autoremove(struct device *host, struct cxl_decoder *cxld);
 | 
					int cxl_decoder_autoremove(struct device *host, struct cxl_decoder *cxld);
 | 
				
			||||||
int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint);
 | 
					int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint);
 | 
				
			||||||
| 
						 | 
					@ -357,6 +593,8 @@ struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port);
 | 
				
			||||||
int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm);
 | 
					int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm);
 | 
				
			||||||
int devm_cxl_add_passthrough_decoder(struct cxl_port *port);
 | 
					int devm_cxl_add_passthrough_decoder(struct cxl_port *port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool is_cxl_region(struct device *dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern struct bus_type cxl_bus_type;
 | 
					extern struct bus_type cxl_bus_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_driver {
 | 
					struct cxl_driver {
 | 
				
			||||||
| 
						 | 
					@ -385,6 +623,8 @@ void cxl_driver_unregister(struct cxl_driver *cxl_drv);
 | 
				
			||||||
#define CXL_DEVICE_PORT			3
 | 
					#define CXL_DEVICE_PORT			3
 | 
				
			||||||
#define CXL_DEVICE_ROOT			4
 | 
					#define CXL_DEVICE_ROOT			4
 | 
				
			||||||
#define CXL_DEVICE_MEMORY_EXPANDER	5
 | 
					#define CXL_DEVICE_MEMORY_EXPANDER	5
 | 
				
			||||||
 | 
					#define CXL_DEVICE_REGION		6
 | 
				
			||||||
 | 
					#define CXL_DEVICE_PMEM_REGION		7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MODULE_ALIAS_CXL(type) MODULE_ALIAS("cxl:t" __stringify(type) "*")
 | 
					#define MODULE_ALIAS_CXL(type) MODULE_ALIAS("cxl:t" __stringify(type) "*")
 | 
				
			||||||
#define CXL_MODALIAS_FMT "cxl:t%d"
 | 
					#define CXL_MODALIAS_FMT "cxl:t%d"
 | 
				
			||||||
| 
						 | 
					@ -396,7 +636,21 @@ struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev);
 | 
				
			||||||
bool is_cxl_nvdimm(struct device *dev);
 | 
					bool is_cxl_nvdimm(struct device *dev);
 | 
				
			||||||
bool is_cxl_nvdimm_bridge(struct device *dev);
 | 
					bool is_cxl_nvdimm_bridge(struct device *dev);
 | 
				
			||||||
int devm_cxl_add_nvdimm(struct device *host, struct cxl_memdev *cxlmd);
 | 
					int devm_cxl_add_nvdimm(struct device *host, struct cxl_memdev *cxlmd);
 | 
				
			||||||
struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_nvdimm *cxl_nvd);
 | 
					struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct device *dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_CXL_REGION
 | 
				
			||||||
 | 
					bool is_cxl_pmem_region(struct device *dev);
 | 
				
			||||||
 | 
					struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static inline bool is_cxl_pmem_region(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					static inline struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Unit test builds overrides this to __weak, find the 'strong' version
 | 
					 * Unit test builds overrides this to __weak, find the 'strong' version
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,24 @@ static inline struct cxl_memdev *to_cxl_memdev(struct device *dev)
 | 
				
			||||||
	return container_of(dev, struct cxl_memdev, dev);
 | 
						return container_of(dev, struct cxl_memdev, dev);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct cxl_port *cxled_to_port(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return to_cxl_port(cxled->cxld.dev.parent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct cxl_port *cxlrd_to_port(struct cxl_root_decoder *cxlrd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return to_cxl_port(cxlrd->cxlsd.cxld.dev.parent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct cxl_memdev *
 | 
				
			||||||
 | 
					cxled_to_memdev(struct cxl_endpoint_decoder *cxled)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(cxled->cxld.dev.parent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to_cxl_memdev(port->uport);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool is_cxl_memdev(struct device *dev);
 | 
					bool is_cxl_memdev(struct device *dev);
 | 
				
			||||||
static inline bool is_cxl_endpoint(struct cxl_port *port)
 | 
					static inline bool is_cxl_endpoint(struct cxl_port *port)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -178,8 +196,9 @@ struct cxl_endpoint_dvsec_info {
 | 
				
			||||||
 * @firmware_version: Firmware version for the memory device.
 | 
					 * @firmware_version: Firmware version for the memory device.
 | 
				
			||||||
 * @enabled_cmds: Hardware commands found enabled in CEL.
 | 
					 * @enabled_cmds: Hardware commands found enabled in CEL.
 | 
				
			||||||
 * @exclusive_cmds: Commands that are kernel-internal only
 | 
					 * @exclusive_cmds: Commands that are kernel-internal only
 | 
				
			||||||
 * @pmem_range: Active Persistent memory capacity configuration
 | 
					 * @dpa_res: Overall DPA resource tree for the device
 | 
				
			||||||
 * @ram_range: Active Volatile memory capacity configuration
 | 
					 * @pmem_res: Active Persistent memory capacity configuration
 | 
				
			||||||
 | 
					 * @ram_res: Active Volatile memory capacity configuration
 | 
				
			||||||
 * @total_bytes: sum of all possible capacities
 | 
					 * @total_bytes: sum of all possible capacities
 | 
				
			||||||
 * @volatile_only_bytes: hard volatile capacity
 | 
					 * @volatile_only_bytes: hard volatile capacity
 | 
				
			||||||
 * @persistent_only_bytes: hard persistent capacity
 | 
					 * @persistent_only_bytes: hard persistent capacity
 | 
				
			||||||
| 
						 | 
					@ -191,6 +210,7 @@ struct cxl_endpoint_dvsec_info {
 | 
				
			||||||
 * @component_reg_phys: register base of component registers
 | 
					 * @component_reg_phys: register base of component registers
 | 
				
			||||||
 * @info: Cached DVSEC information about the device.
 | 
					 * @info: Cached DVSEC information about the device.
 | 
				
			||||||
 * @serial: PCIe Device Serial Number
 | 
					 * @serial: PCIe Device Serial Number
 | 
				
			||||||
 | 
					 * @doe_mbs: PCI DOE mailbox array
 | 
				
			||||||
 * @mbox_send: @dev specific transport for transmitting mailbox commands
 | 
					 * @mbox_send: @dev specific transport for transmitting mailbox commands
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * See section 8.2.9.5.2 Capacity Configuration and Label Storage for
 | 
					 * See section 8.2.9.5.2 Capacity Configuration and Label Storage for
 | 
				
			||||||
| 
						 | 
					@ -209,8 +229,9 @@ struct cxl_dev_state {
 | 
				
			||||||
	DECLARE_BITMAP(enabled_cmds, CXL_MEM_COMMAND_ID_MAX);
 | 
						DECLARE_BITMAP(enabled_cmds, CXL_MEM_COMMAND_ID_MAX);
 | 
				
			||||||
	DECLARE_BITMAP(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX);
 | 
						DECLARE_BITMAP(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct range pmem_range;
 | 
						struct resource dpa_res;
 | 
				
			||||||
	struct range ram_range;
 | 
						struct resource pmem_res;
 | 
				
			||||||
 | 
						struct resource ram_res;
 | 
				
			||||||
	u64 total_bytes;
 | 
						u64 total_bytes;
 | 
				
			||||||
	u64 volatile_only_bytes;
 | 
						u64 volatile_only_bytes;
 | 
				
			||||||
	u64 persistent_only_bytes;
 | 
						u64 persistent_only_bytes;
 | 
				
			||||||
| 
						 | 
					@ -224,6 +245,8 @@ struct cxl_dev_state {
 | 
				
			||||||
	resource_size_t component_reg_phys;
 | 
						resource_size_t component_reg_phys;
 | 
				
			||||||
	u64 serial;
 | 
						u64 serial;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct xarray doe_mbs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int (*mbox_send)(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd);
 | 
						int (*mbox_send)(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -299,6 +322,13 @@ struct cxl_mbox_identify {
 | 
				
			||||||
	u8 qos_telemetry_caps;
 | 
						u8 qos_telemetry_caps;
 | 
				
			||||||
} __packed;
 | 
					} __packed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct cxl_mbox_get_partition_info {
 | 
				
			||||||
 | 
						__le64 active_volatile_cap;
 | 
				
			||||||
 | 
						__le64 active_persistent_cap;
 | 
				
			||||||
 | 
						__le64 next_volatile_cap;
 | 
				
			||||||
 | 
						__le64 next_persistent_cap;
 | 
				
			||||||
 | 
					} __packed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct cxl_mbox_get_lsa {
 | 
					struct cxl_mbox_get_lsa {
 | 
				
			||||||
	__le32 offset;
 | 
						__le32 offset;
 | 
				
			||||||
	__le32 length;
 | 
						__le32 length;
 | 
				
			||||||
| 
						 | 
					@ -370,4 +400,8 @@ struct cxl_hdm {
 | 
				
			||||||
	unsigned int interleave_mask;
 | 
						unsigned int interleave_mask;
 | 
				
			||||||
	struct cxl_port *port;
 | 
						struct cxl_port *port;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct seq_file;
 | 
				
			||||||
 | 
					struct dentry *cxl_debugfs_create_dir(const char *dir);
 | 
				
			||||||
 | 
					void cxl_dpa_debug(struct seq_file *file, struct cxl_dev_state *cxlds);
 | 
				
			||||||
#endif /* __CXL_MEM_H__ */
 | 
					#endif /* __CXL_MEM_H__ */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,4 +74,5 @@ static inline resource_size_t cxl_regmap_to_base(struct pci_dev *pdev,
 | 
				
			||||||
int devm_cxl_port_enumerate_dports(struct cxl_port *port);
 | 
					int devm_cxl_port_enumerate_dports(struct cxl_port *port);
 | 
				
			||||||
struct cxl_dev_state;
 | 
					struct cxl_dev_state;
 | 
				
			||||||
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm);
 | 
					int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm);
 | 
				
			||||||
 | 
					void read_cdat_data(struct cxl_port *port);
 | 
				
			||||||
#endif /* __CXL_PCI_H__ */
 | 
					#endif /* __CXL_PCI_H__ */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
// SPDX-License-Identifier: GPL-2.0-only
 | 
					// SPDX-License-Identifier: GPL-2.0-only
 | 
				
			||||||
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
 | 
					/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
 | 
				
			||||||
 | 
					#include <linux/debugfs.h>
 | 
				
			||||||
#include <linux/device.h>
 | 
					#include <linux/device.h>
 | 
				
			||||||
#include <linux/module.h>
 | 
					#include <linux/module.h>
 | 
				
			||||||
#include <linux/pci.h>
 | 
					#include <linux/pci.h>
 | 
				
			||||||
| 
						 | 
					@ -24,42 +25,32 @@
 | 
				
			||||||
 * in higher level operations.
 | 
					 * in higher level operations.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int create_endpoint(struct cxl_memdev *cxlmd,
 | 
					 | 
				
			||||||
			   struct cxl_port *parent_port)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
					 | 
				
			||||||
	struct cxl_port *endpoint;
 | 
					 | 
				
			||||||
	int rc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	endpoint = devm_cxl_add_port(&parent_port->dev, &cxlmd->dev,
 | 
					 | 
				
			||||||
				     cxlds->component_reg_phys, parent_port);
 | 
					 | 
				
			||||||
	if (IS_ERR(endpoint))
 | 
					 | 
				
			||||||
		return PTR_ERR(endpoint);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	dev_dbg(&cxlmd->dev, "add: %s\n", dev_name(&endpoint->dev));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rc = cxl_endpoint_autoremove(cxlmd, endpoint);
 | 
					 | 
				
			||||||
	if (rc)
 | 
					 | 
				
			||||||
		return rc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!endpoint->dev.driver) {
 | 
					 | 
				
			||||||
		dev_err(&cxlmd->dev, "%s failed probe\n",
 | 
					 | 
				
			||||||
			dev_name(&endpoint->dev));
 | 
					 | 
				
			||||||
		return -ENXIO;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void enable_suspend(void *data)
 | 
					static void enable_suspend(void *data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	cxl_mem_active_dec();
 | 
						cxl_mem_active_dec();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void remove_debugfs(void *dentry)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						debugfs_remove_recursive(dentry);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_mem_dpa_show(struct seq_file *file, void *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = file->private;
 | 
				
			||||||
 | 
						struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxl_dpa_debug(file, cxlmd->cxlds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_mem_probe(struct device *dev)
 | 
					static int cxl_mem_probe(struct device *dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
						struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
 | 
				
			||||||
	struct cxl_port *parent_port;
 | 
						struct cxl_port *parent_port;
 | 
				
			||||||
 | 
						struct cxl_dport *dport;
 | 
				
			||||||
 | 
						struct dentry *dentry;
 | 
				
			||||||
	int rc;
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
| 
						 | 
					@ -73,11 +64,17 @@ static int cxl_mem_probe(struct device *dev)
 | 
				
			||||||
	if (work_pending(&cxlmd->detach_work))
 | 
						if (work_pending(&cxlmd->detach_work))
 | 
				
			||||||
		return -EBUSY;
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dentry = cxl_debugfs_create_dir(dev_name(dev));
 | 
				
			||||||
 | 
						debugfs_create_devm_seqfile(dev, "dpamem", dentry, cxl_mem_dpa_show);
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(dev, remove_debugfs, dentry);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = devm_cxl_enumerate_ports(cxlmd);
 | 
						rc = devm_cxl_enumerate_ports(cxlmd);
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	parent_port = cxl_mem_find_port(cxlmd);
 | 
						parent_port = cxl_mem_find_port(cxlmd, &dport);
 | 
				
			||||||
	if (!parent_port) {
 | 
						if (!parent_port) {
 | 
				
			||||||
		dev_err(dev, "CXL port topology not found\n");
 | 
							dev_err(dev, "CXL port topology not found\n");
 | 
				
			||||||
		return -ENXIO;
 | 
							return -ENXIO;
 | 
				
			||||||
| 
						 | 
					@ -91,7 +88,7 @@ static int cxl_mem_probe(struct device *dev)
 | 
				
			||||||
		goto unlock;
 | 
							goto unlock;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = create_endpoint(cxlmd, parent_port);
 | 
						rc = devm_cxl_add_endpoint(cxlmd, dport);
 | 
				
			||||||
unlock:
 | 
					unlock:
 | 
				
			||||||
	device_unlock(&parent_port->dev);
 | 
						device_unlock(&parent_port->dev);
 | 
				
			||||||
	put_device(&parent_port->dev);
 | 
						put_device(&parent_port->dev);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@
 | 
				
			||||||
#include <linux/mutex.h>
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
#include <linux/list.h>
 | 
					#include <linux/list.h>
 | 
				
			||||||
#include <linux/pci.h>
 | 
					#include <linux/pci.h>
 | 
				
			||||||
 | 
					#include <linux/pci-doe.h>
 | 
				
			||||||
#include <linux/io.h>
 | 
					#include <linux/io.h>
 | 
				
			||||||
#include "cxlmem.h"
 | 
					#include "cxlmem.h"
 | 
				
			||||||
#include "cxlpci.h"
 | 
					#include "cxlpci.h"
 | 
				
			||||||
| 
						 | 
					@ -386,6 +387,47 @@ static int cxl_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
 | 
				
			||||||
	return rc;
 | 
						return rc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxl_pci_destroy_doe(void *mbs)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						xa_destroy(mbs);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void devm_cxl_pci_create_doe(struct cxl_dev_state *cxlds)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = cxlds->dev;
 | 
				
			||||||
 | 
						struct pci_dev *pdev = to_pci_dev(dev);
 | 
				
			||||||
 | 
						u16 off = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xa_init(&cxlds->doe_mbs);
 | 
				
			||||||
 | 
						if (devm_add_action(&pdev->dev, cxl_pci_destroy_doe, &cxlds->doe_mbs)) {
 | 
				
			||||||
 | 
							dev_err(dev, "Failed to create XArray for DOE's\n");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Mailbox creation is best effort.  Higher layers must determine if
 | 
				
			||||||
 | 
						 * the lack of a mailbox for their protocol is a device failure or not.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						pci_doe_for_each_off(pdev, off) {
 | 
				
			||||||
 | 
							struct pci_doe_mb *doe_mb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doe_mb = pcim_doe_create_mb(pdev, off);
 | 
				
			||||||
 | 
							if (IS_ERR(doe_mb)) {
 | 
				
			||||||
 | 
								dev_err(dev, "Failed to create MB object for MB @ %x\n",
 | 
				
			||||||
 | 
									off);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (xa_insert(&cxlds->doe_mbs, off, doe_mb, GFP_KERNEL)) {
 | 
				
			||||||
 | 
								dev_err(dev, "xa_insert failed to insert MB @ %x\n",
 | 
				
			||||||
 | 
									off);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dev_dbg(dev, "Created DOE mailbox @%x\n", off);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 | 
					static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_register_map map;
 | 
						struct cxl_register_map map;
 | 
				
			||||||
| 
						 | 
					@ -434,6 +476,8 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxlds->component_reg_phys = cxl_regmap_to_base(pdev, &map);
 | 
						cxlds->component_reg_phys = cxl_regmap_to_base(pdev, &map);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						devm_cxl_pci_create_doe(cxlds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = cxl_pci_setup_mailbox(cxlds);
 | 
						rc = cxl_pci_setup_mailbox(cxlds);
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		return rc;
 | 
							return rc;
 | 
				
			||||||
| 
						 | 
					@ -454,7 +498,7 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 | 
				
			||||||
	if (IS_ERR(cxlmd))
 | 
						if (IS_ERR(cxlmd))
 | 
				
			||||||
		return PTR_ERR(cxlmd);
 | 
							return PTR_ERR(cxlmd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (range_len(&cxlds->pmem_range) && IS_ENABLED(CONFIG_CXL_PMEM))
 | 
						if (resource_size(&cxlds->pmem_res) && IS_ENABLED(CONFIG_CXL_PMEM))
 | 
				
			||||||
		rc = devm_cxl_add_nvdimm(&pdev->dev, cxlmd);
 | 
							rc = devm_cxl_add_nvdimm(&pdev->dev, cxlmd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return rc;
 | 
						return rc;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@
 | 
				
			||||||
#include <linux/ndctl.h>
 | 
					#include <linux/ndctl.h>
 | 
				
			||||||
#include <linux/async.h>
 | 
					#include <linux/async.h>
 | 
				
			||||||
#include <linux/slab.h>
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/nd.h>
 | 
				
			||||||
#include "cxlmem.h"
 | 
					#include "cxlmem.h"
 | 
				
			||||||
#include "cxl.h"
 | 
					#include "cxl.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +27,23 @@ static void clear_exclusive(void *cxlds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void unregister_nvdimm(void *nvdimm)
 | 
					static void unregister_nvdimm(void *nvdimm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
 | 
				
			||||||
 | 
						struct cxl_nvdimm_bridge *cxl_nvb = cxl_nvd->bridge;
 | 
				
			||||||
 | 
						struct cxl_pmem_region *cxlr_pmem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device_lock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
						cxlr_pmem = cxl_nvd->region;
 | 
				
			||||||
 | 
						dev_set_drvdata(&cxl_nvd->dev, NULL);
 | 
				
			||||||
 | 
						cxl_nvd->region = NULL;
 | 
				
			||||||
 | 
						device_unlock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxlr_pmem) {
 | 
				
			||||||
 | 
							device_release_driver(&cxlr_pmem->dev);
 | 
				
			||||||
 | 
							put_device(&cxlr_pmem->dev);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nvdimm_delete(nvdimm);
 | 
						nvdimm_delete(nvdimm);
 | 
				
			||||||
 | 
						cxl_nvd->bridge = NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_nvdimm_probe(struct device *dev)
 | 
					static int cxl_nvdimm_probe(struct device *dev)
 | 
				
			||||||
| 
						 | 
					@ -39,7 +56,7 @@ static int cxl_nvdimm_probe(struct device *dev)
 | 
				
			||||||
	struct nvdimm *nvdimm;
 | 
						struct nvdimm *nvdimm;
 | 
				
			||||||
	int rc;
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cxl_nvb = cxl_find_nvdimm_bridge(cxl_nvd);
 | 
						cxl_nvb = cxl_find_nvdimm_bridge(dev);
 | 
				
			||||||
	if (!cxl_nvb)
 | 
						if (!cxl_nvb)
 | 
				
			||||||
		return -ENXIO;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,6 +83,7 @@ static int cxl_nvdimm_probe(struct device *dev)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dev_set_drvdata(dev, nvdimm);
 | 
						dev_set_drvdata(dev, nvdimm);
 | 
				
			||||||
 | 
						cxl_nvd->bridge = cxl_nvb;
 | 
				
			||||||
	rc = devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
 | 
						rc = devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
 | 
				
			||||||
out:
 | 
					out:
 | 
				
			||||||
	device_unlock(&cxl_nvb->dev);
 | 
						device_unlock(&cxl_nvb->dev);
 | 
				
			||||||
| 
						 | 
					@ -204,15 +222,38 @@ static bool online_nvdimm_bus(struct cxl_nvdimm_bridge *cxl_nvb)
 | 
				
			||||||
	return cxl_nvb->nvdimm_bus != NULL;
 | 
						return cxl_nvb->nvdimm_bus != NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int cxl_nvdimm_release_driver(struct device *dev, void *data)
 | 
					static int cxl_nvdimm_release_driver(struct device *dev, void *cxl_nvb)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_nvdimm *cxl_nvd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!is_cxl_nvdimm(dev))
 | 
						if (!is_cxl_nvdimm(dev))
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxl_nvd = to_cxl_nvdimm(dev);
 | 
				
			||||||
 | 
						if (cxl_nvd->bridge != cxl_nvb)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device_release_driver(dev);
 | 
						device_release_driver(dev);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void offline_nvdimm_bus(struct nvdimm_bus *nvdimm_bus)
 | 
					static int cxl_pmem_region_release_driver(struct device *dev, void *cxl_nvb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_pmem_region *cxlr_pmem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!is_cxl_pmem_region(dev))
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlr_pmem = to_cxl_pmem_region(dev);
 | 
				
			||||||
 | 
						if (cxlr_pmem->bridge != cxl_nvb)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device_release_driver(dev);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void offline_nvdimm_bus(struct cxl_nvdimm_bridge *cxl_nvb,
 | 
				
			||||||
 | 
								       struct nvdimm_bus *nvdimm_bus)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (!nvdimm_bus)
 | 
						if (!nvdimm_bus)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
| 
						 | 
					@ -222,7 +263,10 @@ static void offline_nvdimm_bus(struct nvdimm_bus *nvdimm_bus)
 | 
				
			||||||
	 * nvdimm_bus_unregister() rips the nvdimm objects out from
 | 
						 * nvdimm_bus_unregister() rips the nvdimm objects out from
 | 
				
			||||||
	 * underneath them.
 | 
						 * underneath them.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	bus_for_each_dev(&cxl_bus_type, NULL, NULL, cxl_nvdimm_release_driver);
 | 
						bus_for_each_dev(&cxl_bus_type, NULL, cxl_nvb,
 | 
				
			||||||
 | 
								 cxl_pmem_region_release_driver);
 | 
				
			||||||
 | 
						bus_for_each_dev(&cxl_bus_type, NULL, cxl_nvb,
 | 
				
			||||||
 | 
								 cxl_nvdimm_release_driver);
 | 
				
			||||||
	nvdimm_bus_unregister(nvdimm_bus);
 | 
						nvdimm_bus_unregister(nvdimm_bus);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -260,7 +304,7 @@ static void cxl_nvb_update_state(struct work_struct *work)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		dev_dbg(&cxl_nvb->dev, "rescan: %d\n", rc);
 | 
							dev_dbg(&cxl_nvb->dev, "rescan: %d\n", rc);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	offline_nvdimm_bus(victim_bus);
 | 
						offline_nvdimm_bus(cxl_nvb, victim_bus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	put_device(&cxl_nvb->dev);
 | 
						put_device(&cxl_nvb->dev);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -315,6 +359,203 @@ static struct cxl_driver cxl_nvdimm_bridge_driver = {
 | 
				
			||||||
	.id = CXL_DEVICE_NVDIMM_BRIDGE,
 | 
						.id = CXL_DEVICE_NVDIMM_BRIDGE,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int match_cxl_nvdimm(struct device *dev, void *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return is_cxl_nvdimm(dev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void unregister_nvdimm_region(void *nd_region)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_nvdimm_bridge *cxl_nvb;
 | 
				
			||||||
 | 
						struct cxl_pmem_region *cxlr_pmem;
 | 
				
			||||||
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlr_pmem = nd_region_provider_data(nd_region);
 | 
				
			||||||
 | 
						cxl_nvb = cxlr_pmem->bridge;
 | 
				
			||||||
 | 
						device_lock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
						for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
 | 
				
			||||||
 | 
							struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
 | 
				
			||||||
 | 
							struct cxl_nvdimm *cxl_nvd = m->cxl_nvd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (cxl_nvd->region) {
 | 
				
			||||||
 | 
								put_device(&cxlr_pmem->dev);
 | 
				
			||||||
 | 
								cxl_nvd->region = NULL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						device_unlock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nvdimm_region_delete(nd_region);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void cxlr_pmem_remove_resource(void *res)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						remove_resource(res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct cxl_pmem_region_info {
 | 
				
			||||||
 | 
						u64 offset;
 | 
				
			||||||
 | 
						u64 serial;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int cxl_pmem_region_probe(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct nd_mapping_desc mappings[CXL_DECODER_MAX_INTERLEAVE];
 | 
				
			||||||
 | 
						struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev);
 | 
				
			||||||
 | 
						struct cxl_region *cxlr = cxlr_pmem->cxlr;
 | 
				
			||||||
 | 
						struct cxl_pmem_region_info *info = NULL;
 | 
				
			||||||
 | 
						struct cxl_nvdimm_bridge *cxl_nvb;
 | 
				
			||||||
 | 
						struct nd_interleave_set *nd_set;
 | 
				
			||||||
 | 
						struct nd_region_desc ndr_desc;
 | 
				
			||||||
 | 
						struct cxl_nvdimm *cxl_nvd;
 | 
				
			||||||
 | 
						struct nvdimm *nvdimm;
 | 
				
			||||||
 | 
						struct resource *res;
 | 
				
			||||||
 | 
						int rc, i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxl_nvb = cxl_find_nvdimm_bridge(&cxlr_pmem->mapping[0].cxlmd->dev);
 | 
				
			||||||
 | 
						if (!cxl_nvb) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "bridge not found\n");
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cxlr_pmem->bridge = cxl_nvb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device_lock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
						if (!cxl_nvb->nvdimm_bus) {
 | 
				
			||||||
 | 
							dev_dbg(dev, "nvdimm bus not found\n");
 | 
				
			||||||
 | 
							rc = -ENXIO;
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memset(&mappings, 0, sizeof(mappings));
 | 
				
			||||||
 | 
						memset(&ndr_desc, 0, sizeof(ndr_desc));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!res) {
 | 
				
			||||||
 | 
							rc = -ENOMEM;
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res->name = "Persistent Memory";
 | 
				
			||||||
 | 
						res->start = cxlr_pmem->hpa_range.start;
 | 
				
			||||||
 | 
						res->end = cxlr_pmem->hpa_range.end;
 | 
				
			||||||
 | 
						res->flags = IORESOURCE_MEM;
 | 
				
			||||||
 | 
						res->desc = IORES_DESC_PERSISTENT_MEMORY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = insert_resource(&iomem_resource, res);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ndr_desc.res = res;
 | 
				
			||||||
 | 
						ndr_desc.provider_data = cxlr_pmem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ndr_desc.numa_node = memory_add_physaddr_to_nid(res->start);
 | 
				
			||||||
 | 
						ndr_desc.target_node = phys_to_target_node(res->start);
 | 
				
			||||||
 | 
						if (ndr_desc.target_node == NUMA_NO_NODE) {
 | 
				
			||||||
 | 
							ndr_desc.target_node = ndr_desc.numa_node;
 | 
				
			||||||
 | 
							dev_dbg(&cxlr->dev, "changing target node from %d to %d",
 | 
				
			||||||
 | 
								NUMA_NO_NODE, ndr_desc.target_node);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!nd_set) {
 | 
				
			||||||
 | 
							rc = -ENOMEM;
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ndr_desc.memregion = cxlr->id;
 | 
				
			||||||
 | 
						set_bit(ND_REGION_CXL, &ndr_desc.flags);
 | 
				
			||||||
 | 
						set_bit(ND_REGION_PERSIST_MEMCTRL, &ndr_desc.flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info = kmalloc_array(cxlr_pmem->nr_mappings, sizeof(*info), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!info) {
 | 
				
			||||||
 | 
							rc = -ENOMEM;
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
 | 
				
			||||||
 | 
							struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
 | 
				
			||||||
 | 
							struct cxl_memdev *cxlmd = m->cxlmd;
 | 
				
			||||||
 | 
							struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
							struct device *d;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							d = device_find_child(&cxlmd->dev, NULL, match_cxl_nvdimm);
 | 
				
			||||||
 | 
							if (!d) {
 | 
				
			||||||
 | 
								dev_dbg(dev, "[%d]: %s: no cxl_nvdimm found\n", i,
 | 
				
			||||||
 | 
									dev_name(&cxlmd->dev));
 | 
				
			||||||
 | 
								rc = -ENODEV;
 | 
				
			||||||
 | 
								goto err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* safe to drop ref now with bridge lock held */
 | 
				
			||||||
 | 
							put_device(d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							cxl_nvd = to_cxl_nvdimm(d);
 | 
				
			||||||
 | 
							nvdimm = dev_get_drvdata(&cxl_nvd->dev);
 | 
				
			||||||
 | 
							if (!nvdimm) {
 | 
				
			||||||
 | 
								dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,
 | 
				
			||||||
 | 
									dev_name(&cxlmd->dev));
 | 
				
			||||||
 | 
								rc = -ENODEV;
 | 
				
			||||||
 | 
								goto err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cxl_nvd->region = cxlr_pmem;
 | 
				
			||||||
 | 
							get_device(&cxlr_pmem->dev);
 | 
				
			||||||
 | 
							m->cxl_nvd = cxl_nvd;
 | 
				
			||||||
 | 
							mappings[i] = (struct nd_mapping_desc) {
 | 
				
			||||||
 | 
								.nvdimm = nvdimm,
 | 
				
			||||||
 | 
								.start = m->start,
 | 
				
			||||||
 | 
								.size = m->size,
 | 
				
			||||||
 | 
								.position = i,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							info[i].offset = m->start;
 | 
				
			||||||
 | 
							info[i].serial = cxlds->serial;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ndr_desc.num_mappings = cxlr_pmem->nr_mappings;
 | 
				
			||||||
 | 
						ndr_desc.mapping = mappings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * TODO enable CXL labels which skip the need for 'interleave-set cookie'
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						nd_set->cookie1 =
 | 
				
			||||||
 | 
							nd_fletcher64(info, sizeof(*info) * cxlr_pmem->nr_mappings, 0);
 | 
				
			||||||
 | 
						nd_set->cookie2 = nd_set->cookie1;
 | 
				
			||||||
 | 
						ndr_desc.nd_set = nd_set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cxlr_pmem->nd_region =
 | 
				
			||||||
 | 
							nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);
 | 
				
			||||||
 | 
						if (!cxlr_pmem->nd_region) {
 | 
				
			||||||
 | 
							rc = -ENOMEM;
 | 
				
			||||||
 | 
							goto err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,
 | 
				
			||||||
 | 
									      cxlr_pmem->nd_region);
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						kfree(info);
 | 
				
			||||||
 | 
						device_unlock(&cxl_nvb->dev);
 | 
				
			||||||
 | 
						put_device(&cxl_nvb->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err:
 | 
				
			||||||
 | 
						dev_dbg(dev, "failed to create nvdimm region\n");
 | 
				
			||||||
 | 
						for (i--; i >= 0; i--) {
 | 
				
			||||||
 | 
							nvdimm = mappings[i].nvdimm;
 | 
				
			||||||
 | 
							cxl_nvd = nvdimm_provider_data(nvdimm);
 | 
				
			||||||
 | 
							put_device(&cxl_nvd->region->dev);
 | 
				
			||||||
 | 
							cxl_nvd->region = NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						goto out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct cxl_driver cxl_pmem_region_driver = {
 | 
				
			||||||
 | 
						.name = "cxl_pmem_region",
 | 
				
			||||||
 | 
						.probe = cxl_pmem_region_probe,
 | 
				
			||||||
 | 
						.id = CXL_DEVICE_PMEM_REGION,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Return all bridges to the CXL_NVB_NEW state to invalidate any
 | 
					 * Return all bridges to the CXL_NVB_NEW state to invalidate any
 | 
				
			||||||
 * ->state_work referring to the now destroyed cxl_pmem_wq.
 | 
					 * ->state_work referring to the now destroyed cxl_pmem_wq.
 | 
				
			||||||
| 
						 | 
					@ -359,8 +600,14 @@ static __init int cxl_pmem_init(void)
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		goto err_nvdimm;
 | 
							goto err_nvdimm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = cxl_driver_register(&cxl_pmem_region_driver);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							goto err_region;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_region:
 | 
				
			||||||
 | 
						cxl_driver_unregister(&cxl_nvdimm_driver);
 | 
				
			||||||
err_nvdimm:
 | 
					err_nvdimm:
 | 
				
			||||||
	cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 | 
						cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 | 
				
			||||||
err_bridge:
 | 
					err_bridge:
 | 
				
			||||||
| 
						 | 
					@ -370,6 +617,7 @@ static __init int cxl_pmem_init(void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static __exit void cxl_pmem_exit(void)
 | 
					static __exit void cxl_pmem_exit(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						cxl_driver_unregister(&cxl_pmem_region_driver);
 | 
				
			||||||
	cxl_driver_unregister(&cxl_nvdimm_driver);
 | 
						cxl_driver_unregister(&cxl_nvdimm_driver);
 | 
				
			||||||
	cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 | 
						cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
 | 
				
			||||||
	destroy_cxl_pmem_wq();
 | 
						destroy_cxl_pmem_wq();
 | 
				
			||||||
| 
						 | 
					@ -381,3 +629,4 @@ module_exit(cxl_pmem_exit);
 | 
				
			||||||
MODULE_IMPORT_NS(CXL);
 | 
					MODULE_IMPORT_NS(CXL);
 | 
				
			||||||
MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
 | 
					MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
 | 
				
			||||||
MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM);
 | 
					MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM);
 | 
				
			||||||
 | 
					MODULE_ALIAS_CXL(CXL_DEVICE_PMEM_REGION);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,9 @@ static int cxl_port_probe(struct device *dev)
 | 
				
			||||||
		struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport);
 | 
							struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport);
 | 
				
			||||||
		struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
							struct cxl_dev_state *cxlds = cxlmd->cxlds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Cache the data early to ensure is_visible() works */
 | 
				
			||||||
 | 
							read_cdat_data(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		get_device(&cxlmd->dev);
 | 
							get_device(&cxlmd->dev);
 | 
				
			||||||
		rc = devm_add_action_or_reset(dev, schedule_detach, cxlmd);
 | 
							rc = devm_add_action_or_reset(dev, schedule_detach, cxlmd);
 | 
				
			||||||
		if (rc)
 | 
							if (rc)
 | 
				
			||||||
| 
						 | 
					@ -78,10 +81,60 @@ static int cxl_port_probe(struct device *dev)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
 | 
				
			||||||
 | 
								 struct bin_attribute *bin_attr, char *buf,
 | 
				
			||||||
 | 
								 loff_t offset, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = kobj_to_dev(kobj);
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!port->cdat_available)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!port->cdat.table)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return memory_read_from_buffer(buf, count, &offset,
 | 
				
			||||||
 | 
									       port->cdat.table,
 | 
				
			||||||
 | 
									       port->cdat.length);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static BIN_ATTR_ADMIN_RO(CDAT, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
 | 
				
			||||||
 | 
										    struct bin_attribute *attr, int i)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = kobj_to_dev(kobj);
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((attr == &bin_attr_CDAT) && port->cdat_available)
 | 
				
			||||||
 | 
							return attr->attr.mode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct bin_attribute *cxl_cdat_bin_attributes[] = {
 | 
				
			||||||
 | 
						&bin_attr_CDAT,
 | 
				
			||||||
 | 
						NULL,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct attribute_group cxl_cdat_attribute_group = {
 | 
				
			||||||
 | 
						.bin_attrs = cxl_cdat_bin_attributes,
 | 
				
			||||||
 | 
						.is_bin_visible = cxl_port_bin_attr_is_visible,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct attribute_group *cxl_port_attribute_groups[] = {
 | 
				
			||||||
 | 
						&cxl_cdat_attribute_group,
 | 
				
			||||||
 | 
						NULL,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct cxl_driver cxl_port_driver = {
 | 
					static struct cxl_driver cxl_port_driver = {
 | 
				
			||||||
	.name = "cxl_port",
 | 
						.name = "cxl_port",
 | 
				
			||||||
	.probe = cxl_port_probe,
 | 
						.probe = cxl_port_probe,
 | 
				
			||||||
	.id = CXL_DEVICE_PORT,
 | 
						.id = CXL_DEVICE_PORT,
 | 
				
			||||||
 | 
						.drv = {
 | 
				
			||||||
 | 
							.dev_groups = cxl_port_attribute_groups,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module_cxl_driver(cxl_port_driver);
 | 
					module_cxl_driver(cxl_port_driver);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,7 +133,8 @@ static void nd_region_release(struct device *dev)
 | 
				
			||||||
		put_device(&nvdimm->dev);
 | 
							put_device(&nvdimm->dev);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	free_percpu(nd_region->lane);
 | 
						free_percpu(nd_region->lane);
 | 
				
			||||||
	memregion_free(nd_region->id);
 | 
						if (!test_bit(ND_REGION_CXL, &nd_region->flags))
 | 
				
			||||||
 | 
							memregion_free(nd_region->id);
 | 
				
			||||||
	kfree(nd_region);
 | 
						kfree(nd_region);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -982,9 +983,14 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!nd_region)
 | 
						if (!nd_region)
 | 
				
			||||||
		return NULL;
 | 
							return NULL;
 | 
				
			||||||
	nd_region->id = memregion_alloc(GFP_KERNEL);
 | 
						/* CXL pre-assigns memregion ids before creating nvdimm regions */
 | 
				
			||||||
	if (nd_region->id < 0)
 | 
						if (test_bit(ND_REGION_CXL, &ndr_desc->flags)) {
 | 
				
			||||||
		goto err_id;
 | 
							nd_region->id = ndr_desc->memregion;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							nd_region->id = memregion_alloc(GFP_KERNEL);
 | 
				
			||||||
 | 
							if (nd_region->id < 0)
 | 
				
			||||||
 | 
								goto err_id;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nd_region->lane = alloc_percpu(struct nd_percpu_lane);
 | 
						nd_region->lane = alloc_percpu(struct nd_percpu_lane);
 | 
				
			||||||
	if (!nd_region->lane)
 | 
						if (!nd_region->lane)
 | 
				
			||||||
| 
						 | 
					@ -1043,9 +1049,10 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nd_region;
 | 
						return nd_region;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 err_percpu:
 | 
					err_percpu:
 | 
				
			||||||
	memregion_free(nd_region->id);
 | 
						if (!test_bit(ND_REGION_CXL, &ndr_desc->flags))
 | 
				
			||||||
 err_id:
 | 
							memregion_free(nd_region->id);
 | 
				
			||||||
 | 
					err_id:
 | 
				
			||||||
	kfree(nd_region);
 | 
						kfree(nd_region);
 | 
				
			||||||
	return NULL;
 | 
						return NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1068,6 +1075,13 @@ struct nd_region *nvdimm_volatile_region_create(struct nvdimm_bus *nvdimm_bus,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(nvdimm_volatile_region_create);
 | 
					EXPORT_SYMBOL_GPL(nvdimm_volatile_region_create);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nvdimm_region_delete(struct nd_region *nd_region)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (nd_region)
 | 
				
			||||||
 | 
							nd_device_unregister(&nd_region->dev, ND_SYNC);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(nvdimm_region_delete);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int nvdimm_flush(struct nd_region *nd_region, struct bio *bio)
 | 
					int nvdimm_flush(struct nd_region *nd_region, struct bio *bio)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int rc = 0;
 | 
						int rc = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,6 +121,9 @@ config XEN_PCIDEV_FRONTEND
 | 
				
			||||||
config PCI_ATS
 | 
					config PCI_ATS
 | 
				
			||||||
	bool
 | 
						bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config PCI_DOE
 | 
				
			||||||
 | 
						bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config PCI_ECAM
 | 
					config PCI_ECAM
 | 
				
			||||||
	bool
 | 
						bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@ obj-$(CONFIG_PCI_ECAM)		+= ecam.o
 | 
				
			||||||
obj-$(CONFIG_PCI_P2PDMA)	+= p2pdma.o
 | 
					obj-$(CONFIG_PCI_P2PDMA)	+= p2pdma.o
 | 
				
			||||||
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
 | 
					obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
 | 
				
			||||||
obj-$(CONFIG_VGA_ARB)		+= vgaarb.o
 | 
					obj-$(CONFIG_VGA_ARB)		+= vgaarb.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_PCI_DOE)		+= doe.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Endpoint library must be initialized before its users
 | 
					# Endpoint library must be initialized before its users
 | 
				
			||||||
obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 | 
					obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										536
									
								
								drivers/pci/doe.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										536
									
								
								drivers/pci/doe.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,536 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Data Object Exchange
 | 
				
			||||||
 | 
					 *	PCIe r6.0, sec 6.30 DOE
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2021 Huawei
 | 
				
			||||||
 | 
					 *	Jonathan Cameron <Jonathan.Cameron@huawei.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2022 Intel Corporation
 | 
				
			||||||
 | 
					 *	Ira Weiny <ira.weiny@intel.com>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define dev_fmt(fmt) "DOE: " fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/bitfield.h>
 | 
				
			||||||
 | 
					#include <linux/delay.h>
 | 
				
			||||||
 | 
					#include <linux/jiffies.h>
 | 
				
			||||||
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
 | 
					#include <linux/pci.h>
 | 
				
			||||||
 | 
					#include <linux/pci-doe.h>
 | 
				
			||||||
 | 
					#include <linux/workqueue.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PCI_DOE_PROTOCOL_DISCOVERY 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Timeout of 1 second from 6.30.2 Operation, PCI Spec r6.0 */
 | 
				
			||||||
 | 
					#define PCI_DOE_TIMEOUT HZ
 | 
				
			||||||
 | 
					#define PCI_DOE_POLL_INTERVAL	(PCI_DOE_TIMEOUT / 128)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PCI_DOE_FLAG_CANCEL	0
 | 
				
			||||||
 | 
					#define PCI_DOE_FLAG_DEAD	1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct pci_doe_mb - State for a single DOE mailbox
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This state is used to manage a single DOE mailbox capability.  All fields
 | 
				
			||||||
 | 
					 * should be considered opaque to the consumers and the structure passed into
 | 
				
			||||||
 | 
					 * the helpers below after being created by devm_pci_doe_create()
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @pdev: PCI device this mailbox belongs to
 | 
				
			||||||
 | 
					 * @cap_offset: Capability offset
 | 
				
			||||||
 | 
					 * @prots: Array of protocols supported (encoded as long values)
 | 
				
			||||||
 | 
					 * @wq: Wait queue for work item
 | 
				
			||||||
 | 
					 * @work_queue: Queue of pci_doe_work items
 | 
				
			||||||
 | 
					 * @flags: Bit array of PCI_DOE_FLAG_* flags
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct pci_doe_mb {
 | 
				
			||||||
 | 
						struct pci_dev *pdev;
 | 
				
			||||||
 | 
						u16 cap_offset;
 | 
				
			||||||
 | 
						struct xarray prots;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wait_queue_head_t wq;
 | 
				
			||||||
 | 
						struct workqueue_struct *work_queue;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_wait(struct pci_doe_mb *doe_mb, unsigned long timeout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (wait_event_timeout(doe_mb->wq,
 | 
				
			||||||
 | 
								       test_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags),
 | 
				
			||||||
 | 
								       timeout))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pci_doe_write_ctrl(struct pci_doe_mb *doe_mb, u32 val)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pci_write_config_dword(pdev, offset + PCI_DOE_CTRL, val);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_abort(struct pci_doe_mb *doe_mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
						unsigned long timeout_jiffies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pci_dbg(pdev, "[%x] Issuing Abort\n", offset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timeout_jiffies = jiffies + PCI_DOE_TIMEOUT;
 | 
				
			||||||
 | 
						pci_doe_write_ctrl(doe_mb, PCI_DOE_CTRL_ABORT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do {
 | 
				
			||||||
 | 
							int rc;
 | 
				
			||||||
 | 
							u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rc = pci_doe_wait(doe_mb, PCI_DOE_POLL_INTERVAL);
 | 
				
			||||||
 | 
							if (rc)
 | 
				
			||||||
 | 
								return rc;
 | 
				
			||||||
 | 
							pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Abort success! */
 | 
				
			||||||
 | 
							if (!FIELD_GET(PCI_DOE_STATUS_ERROR, val) &&
 | 
				
			||||||
 | 
							    !FIELD_GET(PCI_DOE_STATUS_BUSY, val))
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} while (!time_after(jiffies, timeout_jiffies));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Abort has timed out and the MB is dead */
 | 
				
			||||||
 | 
						pci_err(pdev, "[%x] ABORT timed out\n", offset);
 | 
				
			||||||
 | 
						return -EIO;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_send_req(struct pci_doe_mb *doe_mb,
 | 
				
			||||||
 | 
								    struct pci_doe_task *task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Check the DOE busy bit is not set. If it is set, this could indicate
 | 
				
			||||||
 | 
						 * someone other than Linux (e.g. firmware) is using the mailbox. Note
 | 
				
			||||||
 | 
						 * it is expected that firmware and OS will negotiate access rights via
 | 
				
			||||||
 | 
						 * an, as yet to be defined, method.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
 | 
				
			||||||
 | 
						if (FIELD_GET(PCI_DOE_STATUS_BUSY, val))
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (FIELD_GET(PCI_DOE_STATUS_ERROR, val))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Write DOE Header */
 | 
				
			||||||
 | 
						val = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_VID, task->prot.vid) |
 | 
				
			||||||
 | 
							FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, task->prot.type);
 | 
				
			||||||
 | 
						pci_write_config_dword(pdev, offset + PCI_DOE_WRITE, val);
 | 
				
			||||||
 | 
						/* Length is 2 DW of header + length of payload in DW */
 | 
				
			||||||
 | 
						pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
 | 
				
			||||||
 | 
								       FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH,
 | 
				
			||||||
 | 
										  2 + task->request_pl_sz /
 | 
				
			||||||
 | 
											sizeof(u32)));
 | 
				
			||||||
 | 
						for (i = 0; i < task->request_pl_sz / sizeof(u32); i++)
 | 
				
			||||||
 | 
							pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
 | 
				
			||||||
 | 
									       task->request_pl[i]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pci_doe_write_ctrl(doe_mb, PCI_DOE_CTRL_GO);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool pci_doe_data_obj_ready(struct pci_doe_mb *doe_mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
 | 
				
			||||||
 | 
						if (FIELD_GET(PCI_DOE_STATUS_DATA_OBJECT_READY, val))
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_recv_resp(struct pci_doe_mb *doe_mb, struct pci_doe_task *task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
						size_t length, payload_length;
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Read the first dword to get the protocol */
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
 | 
				
			||||||
 | 
						if ((FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, val) != task->prot.vid) ||
 | 
				
			||||||
 | 
						    (FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, val) != task->prot.type)) {
 | 
				
			||||||
 | 
							dev_err_ratelimited(&pdev->dev, "[%x] expected [VID, Protocol] = [%04x, %02x], got [%04x, %02x]\n",
 | 
				
			||||||
 | 
									    doe_mb->cap_offset, task->prot.vid, task->prot.type,
 | 
				
			||||||
 | 
									    FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, val),
 | 
				
			||||||
 | 
									    FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, val));
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
 | 
				
			||||||
 | 
						/* Read the second dword to get the length */
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
 | 
				
			||||||
 | 
						pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						length = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, val);
 | 
				
			||||||
 | 
						if (length > SZ_1M || length < 2)
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* First 2 dwords have already been read */
 | 
				
			||||||
 | 
						length -= 2;
 | 
				
			||||||
 | 
						payload_length = min(length, task->response_pl_sz / sizeof(u32));
 | 
				
			||||||
 | 
						/* Read the rest of the response payload */
 | 
				
			||||||
 | 
						for (i = 0; i < payload_length; i++) {
 | 
				
			||||||
 | 
							pci_read_config_dword(pdev, offset + PCI_DOE_READ,
 | 
				
			||||||
 | 
									      &task->response_pl[i]);
 | 
				
			||||||
 | 
							/* Prior to the last ack, ensure Data Object Ready */
 | 
				
			||||||
 | 
							if (i == (payload_length - 1) && !pci_doe_data_obj_ready(doe_mb))
 | 
				
			||||||
 | 
								return -EIO;
 | 
				
			||||||
 | 
							pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Flush excess length */
 | 
				
			||||||
 | 
						for (; i < length; i++) {
 | 
				
			||||||
 | 
							pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
 | 
				
			||||||
 | 
							pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Final error check to pick up on any since Data Object Ready */
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
 | 
				
			||||||
 | 
						if (FIELD_GET(PCI_DOE_STATUS_ERROR, val))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return min(length, task->response_pl_sz / sizeof(u32)) * sizeof(u32);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void signal_task_complete(struct pci_doe_task *task, int rv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						task->rv = rv;
 | 
				
			||||||
 | 
						task->complete(task);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void signal_task_abort(struct pci_doe_task *task, int rv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb = task->doe_mb;
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (pci_doe_abort(doe_mb)) {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * If the device can't process an abort; set the mailbox dead
 | 
				
			||||||
 | 
							 *	- no more submissions
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							pci_err(pdev, "[%x] Abort failed marking mailbox dead\n",
 | 
				
			||||||
 | 
								doe_mb->cap_offset);
 | 
				
			||||||
 | 
							set_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						signal_task_complete(task, rv);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void doe_statemachine_work(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_task *task = container_of(work, struct pci_doe_task,
 | 
				
			||||||
 | 
											 work);
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb = task->doe_mb;
 | 
				
			||||||
 | 
						struct pci_dev *pdev = doe_mb->pdev;
 | 
				
			||||||
 | 
						int offset = doe_mb->cap_offset;
 | 
				
			||||||
 | 
						unsigned long timeout_jiffies;
 | 
				
			||||||
 | 
						u32 val;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (test_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags)) {
 | 
				
			||||||
 | 
							signal_task_complete(task, -EIO);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Send request */
 | 
				
			||||||
 | 
						rc = pci_doe_send_req(doe_mb, task);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * The specification does not provide any guidance on how to
 | 
				
			||||||
 | 
							 * resolve conflicting requests from other entities.
 | 
				
			||||||
 | 
							 * Furthermore, it is likely that busy will not be detected
 | 
				
			||||||
 | 
							 * most of the time.  Flag any detection of status busy with an
 | 
				
			||||||
 | 
							 * error.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (rc == -EBUSY)
 | 
				
			||||||
 | 
								dev_err_ratelimited(&pdev->dev, "[%x] busy detected; another entity is sending conflicting requests\n",
 | 
				
			||||||
 | 
										    offset);
 | 
				
			||||||
 | 
							signal_task_abort(task, rc);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timeout_jiffies = jiffies + PCI_DOE_TIMEOUT;
 | 
				
			||||||
 | 
						/* Poll for response */
 | 
				
			||||||
 | 
					retry_resp:
 | 
				
			||||||
 | 
						pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
 | 
				
			||||||
 | 
						if (FIELD_GET(PCI_DOE_STATUS_ERROR, val)) {
 | 
				
			||||||
 | 
							signal_task_abort(task, -EIO);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!FIELD_GET(PCI_DOE_STATUS_DATA_OBJECT_READY, val)) {
 | 
				
			||||||
 | 
							if (time_after(jiffies, timeout_jiffies)) {
 | 
				
			||||||
 | 
								signal_task_abort(task, -EIO);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							rc = pci_doe_wait(doe_mb, PCI_DOE_POLL_INTERVAL);
 | 
				
			||||||
 | 
							if (rc) {
 | 
				
			||||||
 | 
								signal_task_abort(task, rc);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							goto retry_resp;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc  = pci_doe_recv_resp(doe_mb, task);
 | 
				
			||||||
 | 
						if (rc < 0) {
 | 
				
			||||||
 | 
							signal_task_abort(task, rc);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						signal_task_complete(task, rc);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pci_doe_task_complete(struct pci_doe_task *task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						complete(task->private);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_discovery(struct pci_doe_mb *doe_mb, u8 *index, u16 *vid,
 | 
				
			||||||
 | 
								     u8 *protocol)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 request_pl = FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_REQ_3_INDEX,
 | 
				
			||||||
 | 
									    *index);
 | 
				
			||||||
 | 
						u32 response_pl;
 | 
				
			||||||
 | 
						DECLARE_COMPLETION_ONSTACK(c);
 | 
				
			||||||
 | 
						struct pci_doe_task task = {
 | 
				
			||||||
 | 
							.prot.vid = PCI_VENDOR_ID_PCI_SIG,
 | 
				
			||||||
 | 
							.prot.type = PCI_DOE_PROTOCOL_DISCOVERY,
 | 
				
			||||||
 | 
							.request_pl = &request_pl,
 | 
				
			||||||
 | 
							.request_pl_sz = sizeof(request_pl),
 | 
				
			||||||
 | 
							.response_pl = &response_pl,
 | 
				
			||||||
 | 
							.response_pl_sz = sizeof(response_pl),
 | 
				
			||||||
 | 
							.complete = pci_doe_task_complete,
 | 
				
			||||||
 | 
							.private = &c,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = pci_doe_submit_task(doe_mb, &task);
 | 
				
			||||||
 | 
						if (rc < 0)
 | 
				
			||||||
 | 
							return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wait_for_completion(&c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (task.rv != sizeof(response_pl))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*vid = FIELD_GET(PCI_DOE_DATA_OBJECT_DISC_RSP_3_VID, response_pl);
 | 
				
			||||||
 | 
						*protocol = FIELD_GET(PCI_DOE_DATA_OBJECT_DISC_RSP_3_PROTOCOL,
 | 
				
			||||||
 | 
								      response_pl);
 | 
				
			||||||
 | 
						*index = FIELD_GET(PCI_DOE_DATA_OBJECT_DISC_RSP_3_NEXT_INDEX,
 | 
				
			||||||
 | 
								   response_pl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void *pci_doe_xa_prot_entry(u16 vid, u8 prot)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return xa_mk_value((vid << 8) | prot);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int pci_doe_cache_protocols(struct pci_doe_mb *doe_mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u8 index = 0;
 | 
				
			||||||
 | 
						u8 xa_idx = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do {
 | 
				
			||||||
 | 
							int rc;
 | 
				
			||||||
 | 
							u16 vid;
 | 
				
			||||||
 | 
							u8 prot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rc = pci_doe_discovery(doe_mb, &index, &vid, &prot);
 | 
				
			||||||
 | 
							if (rc)
 | 
				
			||||||
 | 
								return rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pci_dbg(doe_mb->pdev,
 | 
				
			||||||
 | 
								"[%x] Found protocol %d vid: %x prot: %x\n",
 | 
				
			||||||
 | 
								doe_mb->cap_offset, xa_idx, vid, prot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rc = xa_insert(&doe_mb->prots, xa_idx++,
 | 
				
			||||||
 | 
								       pci_doe_xa_prot_entry(vid, prot), GFP_KERNEL);
 | 
				
			||||||
 | 
							if (rc)
 | 
				
			||||||
 | 
								return rc;
 | 
				
			||||||
 | 
						} while (index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pci_doe_xa_destroy(void *mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb = mb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xa_destroy(&doe_mb->prots);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pci_doe_destroy_workqueue(void *mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb = mb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						destroy_workqueue(doe_mb->work_queue);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pci_doe_flush_mb(void *mb)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb = mb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Stop all pending work items from starting */
 | 
				
			||||||
 | 
						set_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Cancel an in progress work item, if necessary */
 | 
				
			||||||
 | 
						set_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags);
 | 
				
			||||||
 | 
						wake_up(&doe_mb->wq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Flush all work items */
 | 
				
			||||||
 | 
						flush_workqueue(doe_mb->work_queue);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * pcim_doe_create_mb() - Create a DOE mailbox object
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @pdev: PCI device to create the DOE mailbox for
 | 
				
			||||||
 | 
					 * @cap_offset: Offset of the DOE mailbox
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Create a single mailbox object to manage the mailbox protocol at the
 | 
				
			||||||
 | 
					 * cap_offset specified.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * RETURNS: created mailbox object on success
 | 
				
			||||||
 | 
					 *	    ERR_PTR(-errno) on failure
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct pci_doe_mb *pcim_doe_create_mb(struct pci_dev *pdev, u16 cap_offset)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb;
 | 
				
			||||||
 | 
						struct device *dev = &pdev->dev;
 | 
				
			||||||
 | 
						int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						doe_mb = devm_kzalloc(dev, sizeof(*doe_mb), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!doe_mb)
 | 
				
			||||||
 | 
							return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						doe_mb->pdev = pdev;
 | 
				
			||||||
 | 
						doe_mb->cap_offset = cap_offset;
 | 
				
			||||||
 | 
						init_waitqueue_head(&doe_mb->wq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xa_init(&doe_mb->prots);
 | 
				
			||||||
 | 
						rc = devm_add_action(dev, pci_doe_xa_destroy, doe_mb);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return ERR_PTR(rc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						doe_mb->work_queue = alloc_ordered_workqueue("%s %s DOE [%x]", 0,
 | 
				
			||||||
 | 
											dev_driver_string(&pdev->dev),
 | 
				
			||||||
 | 
											pci_name(pdev),
 | 
				
			||||||
 | 
											doe_mb->cap_offset);
 | 
				
			||||||
 | 
						if (!doe_mb->work_queue) {
 | 
				
			||||||
 | 
							pci_err(pdev, "[%x] failed to allocate work queue\n",
 | 
				
			||||||
 | 
								doe_mb->cap_offset);
 | 
				
			||||||
 | 
							return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(dev, pci_doe_destroy_workqueue, doe_mb);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return ERR_PTR(rc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Reset the mailbox by issuing an abort */
 | 
				
			||||||
 | 
						rc = pci_doe_abort(doe_mb);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							pci_err(pdev, "[%x] failed to reset mailbox with abort command : %d\n",
 | 
				
			||||||
 | 
								doe_mb->cap_offset, rc);
 | 
				
			||||||
 | 
							return ERR_PTR(rc);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * The state machine and the mailbox should be in sync now;
 | 
				
			||||||
 | 
						 * Set up mailbox flush prior to using the mailbox to query protocols.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						rc = devm_add_action_or_reset(dev, pci_doe_flush_mb, doe_mb);
 | 
				
			||||||
 | 
						if (rc)
 | 
				
			||||||
 | 
							return ERR_PTR(rc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rc = pci_doe_cache_protocols(doe_mb);
 | 
				
			||||||
 | 
						if (rc) {
 | 
				
			||||||
 | 
							pci_err(pdev, "[%x] failed to cache protocols : %d\n",
 | 
				
			||||||
 | 
								doe_mb->cap_offset, rc);
 | 
				
			||||||
 | 
							return ERR_PTR(rc);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return doe_mb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(pcim_doe_create_mb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * pci_doe_supports_prot() - Return if the DOE instance supports the given
 | 
				
			||||||
 | 
					 *			     protocol
 | 
				
			||||||
 | 
					 * @doe_mb: DOE mailbox capability to query
 | 
				
			||||||
 | 
					 * @vid: Protocol Vendor ID
 | 
				
			||||||
 | 
					 * @type: Protocol type
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * RETURNS: True if the DOE mailbox supports the protocol specified
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool pci_doe_supports_prot(struct pci_doe_mb *doe_mb, u16 vid, u8 type)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned long index;
 | 
				
			||||||
 | 
						void *entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* The discovery protocol must always be supported */
 | 
				
			||||||
 | 
						if (vid == PCI_VENDOR_ID_PCI_SIG && type == PCI_DOE_PROTOCOL_DISCOVERY)
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xa_for_each(&doe_mb->prots, index, entry)
 | 
				
			||||||
 | 
							if (entry == pci_doe_xa_prot_entry(vid, type))
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(pci_doe_supports_prot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * pci_doe_submit_task() - Submit a task to be processed by the state machine
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @doe_mb: DOE mailbox capability to submit to
 | 
				
			||||||
 | 
					 * @task: task to be queued
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Submit a DOE task (request/response) to the DOE mailbox to be processed.
 | 
				
			||||||
 | 
					 * Returns upon queueing the task object.  If the queue is full this function
 | 
				
			||||||
 | 
					 * will sleep until there is room in the queue.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * task->complete will be called when the state machine is done processing this
 | 
				
			||||||
 | 
					 * task.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Excess data will be discarded.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * RETURNS: 0 when task has been successfully queued, -ERRNO on error
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					int pci_doe_submit_task(struct pci_doe_mb *doe_mb, struct pci_doe_task *task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!pci_doe_supports_prot(doe_mb, task->prot.vid, task->prot.type))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * DOE requests must be a whole number of DW and the response needs to
 | 
				
			||||||
 | 
						 * be big enough for at least 1 DW
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (task->request_pl_sz % sizeof(u32) ||
 | 
				
			||||||
 | 
						    task->response_pl_sz < sizeof(u32))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (test_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags))
 | 
				
			||||||
 | 
							return -EIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						task->doe_mb = doe_mb;
 | 
				
			||||||
 | 
						INIT_WORK(&task->work, doe_statemachine_work);
 | 
				
			||||||
 | 
						queue_work(doe_mb->work_queue, &task->work);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(pci_doe_submit_task);
 | 
				
			||||||
| 
						 | 
					@ -2315,7 +2315,7 @@ EXPORT_SYMBOL(pci_alloc_dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool pci_bus_crs_vendor_id(u32 l)
 | 
					static bool pci_bus_crs_vendor_id(u32 l)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return (l & 0xffff) == 0x0001;
 | 
						return (l & 0xffff) == PCI_VENDOR_ID_PCI_SIG;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool pci_bus_wait_crs(struct pci_bus *bus, int devfn, u32 *l,
 | 
					static bool pci_bus_wait_crs(struct pci_bus *bus, int devfn, u32 *l,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,6 +141,7 @@ enum {
 | 
				
			||||||
	IORES_DESC_DEVICE_PRIVATE_MEMORY	= 6,
 | 
						IORES_DESC_DEVICE_PRIVATE_MEMORY	= 6,
 | 
				
			||||||
	IORES_DESC_RESERVED			= 7,
 | 
						IORES_DESC_RESERVED			= 7,
 | 
				
			||||||
	IORES_DESC_SOFT_RESERVED		= 8,
 | 
						IORES_DESC_SOFT_RESERVED		= 8,
 | 
				
			||||||
 | 
						IORES_DESC_CXL				= 9,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -329,6 +330,8 @@ struct resource *devm_request_free_mem_region(struct device *dev,
 | 
				
			||||||
		struct resource *base, unsigned long size);
 | 
							struct resource *base, unsigned long size);
 | 
				
			||||||
struct resource *request_free_mem_region(struct resource *base,
 | 
					struct resource *request_free_mem_region(struct resource *base,
 | 
				
			||||||
		unsigned long size, const char *name);
 | 
							unsigned long size, const char *name);
 | 
				
			||||||
 | 
					struct resource *alloc_free_mem_region(struct resource *base,
 | 
				
			||||||
 | 
							unsigned long size, unsigned long align, const char *name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline void irqresource_disabled(struct resource *res, u32 irq)
 | 
					static inline void irqresource_disabled(struct resource *res, u32 irq)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,6 +59,9 @@ enum {
 | 
				
			||||||
	/* Platform provides asynchronous flush mechanism */
 | 
						/* Platform provides asynchronous flush mechanism */
 | 
				
			||||||
	ND_REGION_ASYNC = 3,
 | 
						ND_REGION_ASYNC = 3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Region was created by CXL subsystem */
 | 
				
			||||||
 | 
						ND_REGION_CXL = 4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* mark newly adjusted resources as requiring a label update */
 | 
						/* mark newly adjusted resources as requiring a label update */
 | 
				
			||||||
	DPA_RESOURCE_ADJUSTED = 1 << 0,
 | 
						DPA_RESOURCE_ADJUSTED = 1 << 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -122,6 +125,7 @@ struct nd_region_desc {
 | 
				
			||||||
	int numa_node;
 | 
						int numa_node;
 | 
				
			||||||
	int target_node;
 | 
						int target_node;
 | 
				
			||||||
	unsigned long flags;
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						int memregion;
 | 
				
			||||||
	struct device_node *of_node;
 | 
						struct device_node *of_node;
 | 
				
			||||||
	int (*flush)(struct nd_region *nd_region, struct bio *bio);
 | 
						int (*flush)(struct nd_region *nd_region, struct bio *bio);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -259,6 +263,7 @@ static inline struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus,
 | 
				
			||||||
			cmd_mask, num_flush, flush_wpq, NULL, NULL, NULL);
 | 
								cmd_mask, num_flush, flush_wpq, NULL, NULL, NULL);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void nvdimm_delete(struct nvdimm *nvdimm);
 | 
					void nvdimm_delete(struct nvdimm *nvdimm);
 | 
				
			||||||
 | 
					void nvdimm_region_delete(struct nd_region *nd_region);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd);
 | 
					const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd);
 | 
				
			||||||
const struct nd_cmd_desc *nd_cmd_bus_desc(int cmd);
 | 
					const struct nd_cmd_desc *nd_cmd_bus_desc(int cmd);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										77
									
								
								include/linux/pci-doe.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								include/linux/pci-doe.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					/* SPDX-License-Identifier: GPL-2.0 */
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Data Object Exchange
 | 
				
			||||||
 | 
					 *	PCIe r6.0, sec 6.30 DOE
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2021 Huawei
 | 
				
			||||||
 | 
					 *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2022 Intel Corporation
 | 
				
			||||||
 | 
					 *	Ira Weiny <ira.weiny@intel.com>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef LINUX_PCI_DOE_H
 | 
				
			||||||
 | 
					#define LINUX_PCI_DOE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct pci_doe_protocol {
 | 
				
			||||||
 | 
						u16 vid;
 | 
				
			||||||
 | 
						u8 type;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct pci_doe_mb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct pci_doe_task - represents a single query/response
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @prot: DOE Protocol
 | 
				
			||||||
 | 
					 * @request_pl: The request payload
 | 
				
			||||||
 | 
					 * @request_pl_sz: Size of the request payload (bytes)
 | 
				
			||||||
 | 
					 * @response_pl: The response payload
 | 
				
			||||||
 | 
					 * @response_pl_sz: Size of the response payload (bytes)
 | 
				
			||||||
 | 
					 * @rv: Return value.  Length of received response or error (bytes)
 | 
				
			||||||
 | 
					 * @complete: Called when task is complete
 | 
				
			||||||
 | 
					 * @private: Private data for the consumer
 | 
				
			||||||
 | 
					 * @work: Used internally by the mailbox
 | 
				
			||||||
 | 
					 * @doe_mb: Used internally by the mailbox
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The payload sizes and rv are specified in bytes with the following
 | 
				
			||||||
 | 
					 * restrictions concerning the protocol.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *	1) The request_pl_sz must be a multiple of double words (4 bytes)
 | 
				
			||||||
 | 
					 *	2) The response_pl_sz must be >= a single double word (4 bytes)
 | 
				
			||||||
 | 
					 *	3) rv is returned as bytes but it will be a multiple of double words
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * NOTE there is no need for the caller to initialize work or doe_mb.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct pci_doe_task {
 | 
				
			||||||
 | 
						struct pci_doe_protocol prot;
 | 
				
			||||||
 | 
						u32 *request_pl;
 | 
				
			||||||
 | 
						size_t request_pl_sz;
 | 
				
			||||||
 | 
						u32 *response_pl;
 | 
				
			||||||
 | 
						size_t response_pl_sz;
 | 
				
			||||||
 | 
						int rv;
 | 
				
			||||||
 | 
						void (*complete)(struct pci_doe_task *task);
 | 
				
			||||||
 | 
						void *private;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* No need for the user to initialize these fields */
 | 
				
			||||||
 | 
						struct work_struct work;
 | 
				
			||||||
 | 
						struct pci_doe_mb *doe_mb;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * pci_doe_for_each_off - Iterate each DOE capability
 | 
				
			||||||
 | 
					 * @pdev: struct pci_dev to iterate
 | 
				
			||||||
 | 
					 * @off: u16 of config space offset of each mailbox capability found
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define pci_doe_for_each_off(pdev, off) \
 | 
				
			||||||
 | 
						for (off = pci_find_next_ext_capability(pdev, off, \
 | 
				
			||||||
 | 
										PCI_EXT_CAP_ID_DOE); \
 | 
				
			||||||
 | 
							off > 0; \
 | 
				
			||||||
 | 
							off = pci_find_next_ext_capability(pdev, off, \
 | 
				
			||||||
 | 
										PCI_EXT_CAP_ID_DOE))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct pci_doe_mb *pcim_doe_create_mb(struct pci_dev *pdev, u16 cap_offset);
 | 
				
			||||||
 | 
					bool pci_doe_supports_prot(struct pci_doe_mb *doe_mb, u16 vid, u8 type);
 | 
				
			||||||
 | 
					int pci_doe_submit_task(struct pci_doe_mb *doe_mb, struct pci_doe_task *task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					@ -151,6 +151,7 @@
 | 
				
			||||||
#define PCI_CLASS_OTHERS		0xff
 | 
					#define PCI_CLASS_OTHERS		0xff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Vendors and devices.  Sort key: vendor first, device next. */
 | 
					/* Vendors and devices.  Sort key: vendor first, device next. */
 | 
				
			||||||
 | 
					#define PCI_VENDOR_ID_PCI_SIG		0x0001
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PCI_VENDOR_ID_LOONGSON		0x0014
 | 
					#define PCI_VENDOR_ID_LOONGSON		0x0014
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -235,6 +235,22 @@ struct bin_attribute bin_attr_##_name = __BIN_ATTR_WO(_name, _size)
 | 
				
			||||||
#define BIN_ATTR_RW(_name, _size)					\
 | 
					#define BIN_ATTR_RW(_name, _size)					\
 | 
				
			||||||
struct bin_attribute bin_attr_##_name = __BIN_ATTR_RW(_name, _size)
 | 
					struct bin_attribute bin_attr_##_name = __BIN_ATTR_RW(_name, _size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define __BIN_ATTR_ADMIN_RO(_name, _size) {					\
 | 
				
			||||||
 | 
						.attr	= { .name = __stringify(_name), .mode = 0400 },		\
 | 
				
			||||||
 | 
						.read	= _name##_read,						\
 | 
				
			||||||
 | 
						.size	= _size,						\
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define __BIN_ATTR_ADMIN_RW(_name, _size)					\
 | 
				
			||||||
 | 
						__BIN_ATTR(_name, 0600, _name##_read, _name##_write, _size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define BIN_ATTR_ADMIN_RO(_name, _size)					\
 | 
				
			||||||
 | 
					struct bin_attribute bin_attr_##_name = __BIN_ATTR_ADMIN_RO(_name, _size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define BIN_ATTR_ADMIN_RW(_name, _size)					\
 | 
				
			||||||
 | 
					struct bin_attribute bin_attr_##_name = __BIN_ATTR_ADMIN_RW(_name, _size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct sysfs_ops {
 | 
					struct sysfs_ops {
 | 
				
			||||||
	ssize_t	(*show)(struct kobject *, struct attribute *, char *);
 | 
						ssize_t	(*show)(struct kobject *, struct attribute *, char *);
 | 
				
			||||||
	ssize_t	(*store)(struct kobject *, struct attribute *, const char *, size_t);
 | 
						ssize_t	(*store)(struct kobject *, struct attribute *, const char *, size_t);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -737,7 +737,8 @@
 | 
				
			||||||
#define PCI_EXT_CAP_ID_DVSEC	0x23	/* Designated Vendor-Specific */
 | 
					#define PCI_EXT_CAP_ID_DVSEC	0x23	/* Designated Vendor-Specific */
 | 
				
			||||||
#define PCI_EXT_CAP_ID_DLF	0x25	/* Data Link Feature */
 | 
					#define PCI_EXT_CAP_ID_DLF	0x25	/* Data Link Feature */
 | 
				
			||||||
#define PCI_EXT_CAP_ID_PL_16GT	0x26	/* Physical Layer 16.0 GT/s */
 | 
					#define PCI_EXT_CAP_ID_PL_16GT	0x26	/* Physical Layer 16.0 GT/s */
 | 
				
			||||||
#define PCI_EXT_CAP_ID_MAX	PCI_EXT_CAP_ID_PL_16GT
 | 
					#define PCI_EXT_CAP_ID_DOE	0x2E	/* Data Object Exchange */
 | 
				
			||||||
 | 
					#define PCI_EXT_CAP_ID_MAX	PCI_EXT_CAP_ID_DOE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PCI_EXT_CAP_DSN_SIZEOF	12
 | 
					#define PCI_EXT_CAP_DSN_SIZEOF	12
 | 
				
			||||||
#define PCI_EXT_CAP_MCAST_ENDPOINT_SIZEOF 40
 | 
					#define PCI_EXT_CAP_MCAST_ENDPOINT_SIZEOF 40
 | 
				
			||||||
| 
						 | 
					@ -1103,4 +1104,30 @@
 | 
				
			||||||
#define  PCI_PL_16GT_LE_CTRL_USP_TX_PRESET_MASK		0x000000F0
 | 
					#define  PCI_PL_16GT_LE_CTRL_USP_TX_PRESET_MASK		0x000000F0
 | 
				
			||||||
#define  PCI_PL_16GT_LE_CTRL_USP_TX_PRESET_SHIFT	4
 | 
					#define  PCI_PL_16GT_LE_CTRL_USP_TX_PRESET_SHIFT	4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Data Object Exchange */
 | 
				
			||||||
 | 
					#define PCI_DOE_CAP		0x04    /* DOE Capabilities Register */
 | 
				
			||||||
 | 
					#define  PCI_DOE_CAP_INT_SUP			0x00000001  /* Interrupt Support */
 | 
				
			||||||
 | 
					#define  PCI_DOE_CAP_INT_MSG_NUM		0x00000ffe  /* Interrupt Message Number */
 | 
				
			||||||
 | 
					#define PCI_DOE_CTRL		0x08    /* DOE Control Register */
 | 
				
			||||||
 | 
					#define  PCI_DOE_CTRL_ABORT			0x00000001  /* DOE Abort */
 | 
				
			||||||
 | 
					#define  PCI_DOE_CTRL_INT_EN			0x00000002  /* DOE Interrupt Enable */
 | 
				
			||||||
 | 
					#define  PCI_DOE_CTRL_GO			0x80000000  /* DOE Go */
 | 
				
			||||||
 | 
					#define PCI_DOE_STATUS		0x0c    /* DOE Status Register */
 | 
				
			||||||
 | 
					#define  PCI_DOE_STATUS_BUSY			0x00000001  /* DOE Busy */
 | 
				
			||||||
 | 
					#define  PCI_DOE_STATUS_INT_STATUS		0x00000002  /* DOE Interrupt Status */
 | 
				
			||||||
 | 
					#define  PCI_DOE_STATUS_ERROR			0x00000004  /* DOE Error */
 | 
				
			||||||
 | 
					#define  PCI_DOE_STATUS_DATA_OBJECT_READY	0x80000000  /* Data Object Ready */
 | 
				
			||||||
 | 
					#define PCI_DOE_WRITE		0x10    /* DOE Write Data Mailbox Register */
 | 
				
			||||||
 | 
					#define PCI_DOE_READ		0x14    /* DOE Read Data Mailbox Register */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* DOE Data Object - note not actually registers */
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_HEADER_1_VID		0x0000ffff
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_HEADER_1_TYPE		0x00ff0000
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH		0x0003ffff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_DISC_REQ_3_INDEX		0x000000ff
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_DISC_RSP_3_VID		0x0000ffff
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_DISC_RSP_3_PROTOCOL		0x00ff0000
 | 
				
			||||||
 | 
					#define PCI_DOE_DATA_OBJECT_DISC_RSP_3_NEXT_INDEX	0xff000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* LINUX_PCI_REGS_H */
 | 
					#endif /* LINUX_PCI_REGS_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -489,8 +489,9 @@ int __weak page_is_ram(unsigned long pfn)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(page_is_ram);
 | 
					EXPORT_SYMBOL_GPL(page_is_ram);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __region_intersects(resource_size_t start, size_t size,
 | 
					static int __region_intersects(struct resource *parent, resource_size_t start,
 | 
				
			||||||
			unsigned long flags, unsigned long desc)
 | 
								       size_t size, unsigned long flags,
 | 
				
			||||||
 | 
								       unsigned long desc)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct resource res;
 | 
						struct resource res;
 | 
				
			||||||
	int type = 0; int other = 0;
 | 
						int type = 0; int other = 0;
 | 
				
			||||||
| 
						 | 
					@ -499,7 +500,7 @@ static int __region_intersects(resource_size_t start, size_t size,
 | 
				
			||||||
	res.start = start;
 | 
						res.start = start;
 | 
				
			||||||
	res.end = start + size - 1;
 | 
						res.end = start + size - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (p = iomem_resource.child; p ; p = p->sibling) {
 | 
						for (p = parent->child; p ; p = p->sibling) {
 | 
				
			||||||
		bool is_type = (((p->flags & flags) == flags) &&
 | 
							bool is_type = (((p->flags & flags) == flags) &&
 | 
				
			||||||
				((desc == IORES_DESC_NONE) ||
 | 
									((desc == IORES_DESC_NONE) ||
 | 
				
			||||||
				 (desc == p->desc)));
 | 
									 (desc == p->desc)));
 | 
				
			||||||
| 
						 | 
					@ -543,7 +544,7 @@ int region_intersects(resource_size_t start, size_t size, unsigned long flags,
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	read_lock(&resource_lock);
 | 
						read_lock(&resource_lock);
 | 
				
			||||||
	ret = __region_intersects(start, size, flags, desc);
 | 
						ret = __region_intersects(&iomem_resource, start, size, flags, desc);
 | 
				
			||||||
	read_unlock(&resource_lock);
 | 
						read_unlock(&resource_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
| 
						 | 
					@ -891,6 +892,13 @@ void insert_resource_expand_to_fit(struct resource *root, struct resource *new)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	write_unlock(&resource_lock);
 | 
						write_unlock(&resource_lock);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Not for general consumption, only early boot memory map parsing, PCI
 | 
				
			||||||
 | 
					 * resource discovery, and late discovery of CXL resources are expected
 | 
				
			||||||
 | 
					 * to use this interface. The former are built-in and only the latter,
 | 
				
			||||||
 | 
					 * CXL, is a module.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_NS_GPL(insert_resource_expand_to_fit, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * remove_resource - Remove a resource in the resource tree
 | 
					 * remove_resource - Remove a resource in the resource tree
 | 
				
			||||||
| 
						 | 
					@ -1773,62 +1781,139 @@ void resource_list_free(struct list_head *head)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL(resource_list_free);
 | 
					EXPORT_SYMBOL(resource_list_free);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef CONFIG_DEVICE_PRIVATE
 | 
					#ifdef CONFIG_GET_FREE_REGION
 | 
				
			||||||
static struct resource *__request_free_mem_region(struct device *dev,
 | 
					#define GFR_DESCENDING		(1UL << 0)
 | 
				
			||||||
		struct resource *base, unsigned long size, const char *name)
 | 
					#define GFR_REQUEST_REGION	(1UL << 1)
 | 
				
			||||||
 | 
					#define GFR_DEFAULT_ALIGN (1UL << PA_SECTION_SHIFT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static resource_size_t gfr_start(struct resource *base, resource_size_t size,
 | 
				
			||||||
 | 
									 resource_size_t align, unsigned long flags)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	resource_size_t end, addr;
 | 
						if (flags & GFR_DESCENDING) {
 | 
				
			||||||
 | 
							resource_size_t end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							end = min_t(resource_size_t, base->end,
 | 
				
			||||||
 | 
								    (1ULL << MAX_PHYSMEM_BITS) - 1);
 | 
				
			||||||
 | 
							return end - size + 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ALIGN(base->start, align);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool gfr_continue(struct resource *base, resource_size_t addr,
 | 
				
			||||||
 | 
								 resource_size_t size, unsigned long flags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (flags & GFR_DESCENDING)
 | 
				
			||||||
 | 
							return addr > size && addr >= base->start;
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * In the ascend case be careful that the last increment by
 | 
				
			||||||
 | 
						 * @size did not wrap 0.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						return addr > addr - size &&
 | 
				
			||||||
 | 
						       addr <= min_t(resource_size_t, base->end,
 | 
				
			||||||
 | 
								     (1ULL << MAX_PHYSMEM_BITS) - 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static resource_size_t gfr_next(resource_size_t addr, resource_size_t size,
 | 
				
			||||||
 | 
									unsigned long flags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (flags & GFR_DESCENDING)
 | 
				
			||||||
 | 
							return addr - size;
 | 
				
			||||||
 | 
						return addr + size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void remove_free_mem_region(void *_res)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct resource *res = _res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (res->parent)
 | 
				
			||||||
 | 
							remove_resource(res);
 | 
				
			||||||
 | 
						free_resource(res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct resource *
 | 
				
			||||||
 | 
					get_free_mem_region(struct device *dev, struct resource *base,
 | 
				
			||||||
 | 
							    resource_size_t size, const unsigned long align,
 | 
				
			||||||
 | 
							    const char *name, const unsigned long desc,
 | 
				
			||||||
 | 
							    const unsigned long flags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						resource_size_t addr;
 | 
				
			||||||
	struct resource *res;
 | 
						struct resource *res;
 | 
				
			||||||
	struct region_devres *dr = NULL;
 | 
						struct region_devres *dr = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size = ALIGN(size, 1UL << PA_SECTION_SHIFT);
 | 
						size = ALIGN(size, align);
 | 
				
			||||||
	end = min_t(unsigned long, base->end, (1UL << MAX_PHYSMEM_BITS) - 1);
 | 
					 | 
				
			||||||
	addr = end - size + 1UL;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res = alloc_resource(GFP_KERNEL);
 | 
						res = alloc_resource(GFP_KERNEL);
 | 
				
			||||||
	if (!res)
 | 
						if (!res)
 | 
				
			||||||
		return ERR_PTR(-ENOMEM);
 | 
							return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (dev) {
 | 
						if (dev && (flags & GFR_REQUEST_REGION)) {
 | 
				
			||||||
		dr = devres_alloc(devm_region_release,
 | 
							dr = devres_alloc(devm_region_release,
 | 
				
			||||||
				sizeof(struct region_devres), GFP_KERNEL);
 | 
									sizeof(struct region_devres), GFP_KERNEL);
 | 
				
			||||||
		if (!dr) {
 | 
							if (!dr) {
 | 
				
			||||||
			free_resource(res);
 | 
								free_resource(res);
 | 
				
			||||||
			return ERR_PTR(-ENOMEM);
 | 
								return ERR_PTR(-ENOMEM);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						} else if (dev) {
 | 
				
			||||||
 | 
							if (devm_add_action_or_reset(dev, remove_free_mem_region, res))
 | 
				
			||||||
 | 
								return ERR_PTR(-ENOMEM);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	write_lock(&resource_lock);
 | 
						write_lock(&resource_lock);
 | 
				
			||||||
	for (; addr > size && addr >= base->start; addr -= size) {
 | 
						for (addr = gfr_start(base, size, align, flags);
 | 
				
			||||||
		if (__region_intersects(addr, size, 0, IORES_DESC_NONE) !=
 | 
						     gfr_continue(base, addr, size, flags);
 | 
				
			||||||
				REGION_DISJOINT)
 | 
						     addr = gfr_next(addr, size, flags)) {
 | 
				
			||||||
 | 
							if (__region_intersects(base, addr, size, 0, IORES_DESC_NONE) !=
 | 
				
			||||||
 | 
							    REGION_DISJOINT)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (__request_region_locked(res, &iomem_resource, addr, size,
 | 
							if (flags & GFR_REQUEST_REGION) {
 | 
				
			||||||
						name, 0))
 | 
								if (__request_region_locked(res, &iomem_resource, addr,
 | 
				
			||||||
			break;
 | 
											    size, name, 0))
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (dev) {
 | 
								if (dev) {
 | 
				
			||||||
			dr->parent = &iomem_resource;
 | 
									dr->parent = &iomem_resource;
 | 
				
			||||||
			dr->start = addr;
 | 
									dr->start = addr;
 | 
				
			||||||
			dr->n = size;
 | 
									dr->n = size;
 | 
				
			||||||
			devres_add(dev, dr);
 | 
									devres_add(dev, dr);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								res->desc = desc;
 | 
				
			||||||
 | 
								write_unlock(&resource_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
								 * A driver is claiming this region so revoke any
 | 
				
			||||||
 | 
								 * mappings.
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								revoke_iomem(res);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res->start = addr;
 | 
				
			||||||
 | 
								res->end = addr + size - 1;
 | 
				
			||||||
 | 
								res->name = name;
 | 
				
			||||||
 | 
								res->desc = desc;
 | 
				
			||||||
 | 
								res->flags = IORESOURCE_MEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
								 * Only succeed if the resource hosts an exclusive
 | 
				
			||||||
 | 
								 * range after the insert
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								if (__insert_resource(base, res) || res->child)
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								write_unlock(&resource_lock);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		res->desc = IORES_DESC_DEVICE_PRIVATE_MEMORY;
 | 
					 | 
				
			||||||
		write_unlock(&resource_lock);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/*
 | 
					 | 
				
			||||||
		 * A driver is claiming this region so revoke any mappings.
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		revoke_iomem(res);
 | 
					 | 
				
			||||||
		return res;
 | 
							return res;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	write_unlock(&resource_lock);
 | 
						write_unlock(&resource_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	free_resource(res);
 | 
						if (flags & GFR_REQUEST_REGION) {
 | 
				
			||||||
	if (dr)
 | 
							free_resource(res);
 | 
				
			||||||
		devres_free(dr);
 | 
							devres_free(dr);
 | 
				
			||||||
 | 
						} else if (dev)
 | 
				
			||||||
 | 
							devm_release_action(dev, remove_free_mem_region, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ERR_PTR(-ERANGE);
 | 
						return ERR_PTR(-ERANGE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1847,18 +1932,48 @@ static struct resource *__request_free_mem_region(struct device *dev,
 | 
				
			||||||
struct resource *devm_request_free_mem_region(struct device *dev,
 | 
					struct resource *devm_request_free_mem_region(struct device *dev,
 | 
				
			||||||
		struct resource *base, unsigned long size)
 | 
							struct resource *base, unsigned long size)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return __request_free_mem_region(dev, base, size, dev_name(dev));
 | 
						unsigned long flags = GFR_DESCENDING | GFR_REQUEST_REGION;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return get_free_mem_region(dev, base, size, GFR_DEFAULT_ALIGN,
 | 
				
			||||||
 | 
									   dev_name(dev),
 | 
				
			||||||
 | 
									   IORES_DESC_DEVICE_PRIVATE_MEMORY, flags);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(devm_request_free_mem_region);
 | 
					EXPORT_SYMBOL_GPL(devm_request_free_mem_region);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct resource *request_free_mem_region(struct resource *base,
 | 
					struct resource *request_free_mem_region(struct resource *base,
 | 
				
			||||||
		unsigned long size, const char *name)
 | 
							unsigned long size, const char *name)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return __request_free_mem_region(NULL, base, size, name);
 | 
						unsigned long flags = GFR_DESCENDING | GFR_REQUEST_REGION;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return get_free_mem_region(NULL, base, size, GFR_DEFAULT_ALIGN, name,
 | 
				
			||||||
 | 
									   IORES_DESC_DEVICE_PRIVATE_MEMORY, flags);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(request_free_mem_region);
 | 
					EXPORT_SYMBOL_GPL(request_free_mem_region);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* CONFIG_DEVICE_PRIVATE */
 | 
					/**
 | 
				
			||||||
 | 
					 * alloc_free_mem_region - find a free region relative to @base
 | 
				
			||||||
 | 
					 * @base: resource that will parent the new resource
 | 
				
			||||||
 | 
					 * @size: size in bytes of memory to allocate from @base
 | 
				
			||||||
 | 
					 * @align: alignment requirements for the allocation
 | 
				
			||||||
 | 
					 * @name: resource name
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Buses like CXL, that can dynamically instantiate new memory regions,
 | 
				
			||||||
 | 
					 * need a method to allocate physical address space for those regions.
 | 
				
			||||||
 | 
					 * Allocate and insert a new resource to cover a free, unclaimed by a
 | 
				
			||||||
 | 
					 * descendant of @base, range in the span of @base.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct resource *alloc_free_mem_region(struct resource *base,
 | 
				
			||||||
 | 
									       unsigned long size, unsigned long align,
 | 
				
			||||||
 | 
									       const char *name)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/* Default of ascending direction and insert resource */
 | 
				
			||||||
 | 
						unsigned long flags = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return get_free_mem_region(NULL, base, size, align, name,
 | 
				
			||||||
 | 
									   IORES_DESC_NONE, flags);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_NS_GPL(alloc_free_mem_region, CXL);
 | 
				
			||||||
 | 
					#endif /* CONFIG_GET_FREE_REGION */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __init strict_iomem(char *str)
 | 
					static int __init strict_iomem(char *str)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -983,9 +983,14 @@ config HMM_MIRROR
 | 
				
			||||||
	bool
 | 
						bool
 | 
				
			||||||
	depends on MMU
 | 
						depends on MMU
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config GET_FREE_REGION
 | 
				
			||||||
 | 
						depends on SPARSEMEM
 | 
				
			||||||
 | 
						bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config DEVICE_PRIVATE
 | 
					config DEVICE_PRIVATE
 | 
				
			||||||
	bool "Unaddressable device memory (GPU memory, ...)"
 | 
						bool "Unaddressable device memory (GPU memory, ...)"
 | 
				
			||||||
	depends on ZONE_DEVICE
 | 
						depends on ZONE_DEVICE
 | 
				
			||||||
 | 
						select GET_FREE_REGION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	  Allows creation of struct pages to represent unaddressable device
 | 
						  Allows creation of struct pages to represent unaddressable device
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,7 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o
 | 
				
			||||||
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
 | 
					cxl_core-y += $(CXL_CORE_SRC)/mbox.o
 | 
				
			||||||
cxl_core-y += $(CXL_CORE_SRC)/pci.o
 | 
					cxl_core-y += $(CXL_CORE_SRC)/pci.o
 | 
				
			||||||
cxl_core-y += $(CXL_CORE_SRC)/hdm.o
 | 
					cxl_core-y += $(CXL_CORE_SRC)/hdm.o
 | 
				
			||||||
 | 
					cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o
 | 
				
			||||||
cxl_core-y += config_check.o
 | 
					cxl_core-y += config_check.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
obj-m += test/
 | 
					obj-m += test/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
#define NR_CXL_HOST_BRIDGES 2
 | 
					#define NR_CXL_HOST_BRIDGES 2
 | 
				
			||||||
#define NR_CXL_ROOT_PORTS 2
 | 
					#define NR_CXL_ROOT_PORTS 2
 | 
				
			||||||
#define NR_CXL_SWITCH_PORTS 2
 | 
					#define NR_CXL_SWITCH_PORTS 2
 | 
				
			||||||
#define NR_CXL_PORT_DECODERS 2
 | 
					#define NR_CXL_PORT_DECODERS 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct platform_device *cxl_acpi;
 | 
					static struct platform_device *cxl_acpi;
 | 
				
			||||||
static struct platform_device *cxl_host_bridge[NR_CXL_HOST_BRIDGES];
 | 
					static struct platform_device *cxl_host_bridge[NR_CXL_HOST_BRIDGES];
 | 
				
			||||||
| 
						 | 
					@ -118,7 +118,7 @@ static struct {
 | 
				
			||||||
			.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
								.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
				
			||||||
					ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
 | 
										ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
 | 
				
			||||||
			.qtg_id = 0,
 | 
								.qtg_id = 0,
 | 
				
			||||||
			.window_size = SZ_256M,
 | 
								.window_size = SZ_256M * 4UL,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		.target = { 0 },
 | 
							.target = { 0 },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -133,7 +133,7 @@ static struct {
 | 
				
			||||||
			.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
								.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
				
			||||||
					ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
 | 
										ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
 | 
				
			||||||
			.qtg_id = 1,
 | 
								.qtg_id = 1,
 | 
				
			||||||
			.window_size = SZ_256M * 2,
 | 
								.window_size = SZ_256M * 8UL,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		.target = { 0, 1, },
 | 
							.target = { 0, 1, },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -148,7 +148,7 @@ static struct {
 | 
				
			||||||
			.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
								.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
				
			||||||
					ACPI_CEDT_CFMWS_RESTRICT_PMEM,
 | 
										ACPI_CEDT_CFMWS_RESTRICT_PMEM,
 | 
				
			||||||
			.qtg_id = 2,
 | 
								.qtg_id = 2,
 | 
				
			||||||
			.window_size = SZ_256M,
 | 
								.window_size = SZ_256M * 4UL,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		.target = { 0 },
 | 
							.target = { 0 },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -163,7 +163,7 @@ static struct {
 | 
				
			||||||
			.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
								.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
 | 
				
			||||||
					ACPI_CEDT_CFMWS_RESTRICT_PMEM,
 | 
										ACPI_CEDT_CFMWS_RESTRICT_PMEM,
 | 
				
			||||||
			.qtg_id = 3,
 | 
								.qtg_id = 3,
 | 
				
			||||||
			.window_size = SZ_256M * 2,
 | 
								.window_size = SZ_256M * 8UL,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		.target = { 0, 1, },
 | 
							.target = { 0, 1, },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -429,6 +429,50 @@ static int map_targets(struct device *dev, void *data)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int mock_decoder_commit(struct cxl_decoder *cxld)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(cxld->dev.parent);
 | 
				
			||||||
 | 
						int id = cxld->id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cxld->flags & CXL_DECODER_F_ENABLE)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(&port->dev, "%s commit\n", dev_name(&cxld->dev));
 | 
				
			||||||
 | 
						if (port->commit_end + 1 != id) {
 | 
				
			||||||
 | 
							dev_dbg(&port->dev,
 | 
				
			||||||
 | 
								"%s: out of order commit, expected decoder%d.%d\n",
 | 
				
			||||||
 | 
								dev_name(&cxld->dev), port->id, port->commit_end + 1);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->commit_end++;
 | 
				
			||||||
 | 
						cxld->flags |= CXL_DECODER_F_ENABLE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int mock_decoder_reset(struct cxl_decoder *cxld)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_port *port = to_cxl_port(cxld->dev.parent);
 | 
				
			||||||
 | 
						int id = cxld->id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(&port->dev, "%s reset\n", dev_name(&cxld->dev));
 | 
				
			||||||
 | 
						if (port->commit_end != id) {
 | 
				
			||||||
 | 
							dev_dbg(&port->dev,
 | 
				
			||||||
 | 
								"%s: out of order reset, expected decoder%d.%d\n",
 | 
				
			||||||
 | 
								dev_name(&cxld->dev), port->id, port->commit_end);
 | 
				
			||||||
 | 
							return -EBUSY;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						port->commit_end--;
 | 
				
			||||||
 | 
						cxld->flags &= ~CXL_DECODER_F_ENABLE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
					static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_port *port = cxlhdm->port;
 | 
						struct cxl_port *port = cxlhdm->port;
 | 
				
			||||||
| 
						 | 
					@ -451,25 +495,39 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
 | 
				
			||||||
		struct cxl_decoder *cxld;
 | 
							struct cxl_decoder *cxld;
 | 
				
			||||||
		int rc;
 | 
							int rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (target_count)
 | 
							if (target_count) {
 | 
				
			||||||
			cxld = cxl_switch_decoder_alloc(port, target_count);
 | 
								struct cxl_switch_decoder *cxlsd;
 | 
				
			||||||
		else
 | 
					
 | 
				
			||||||
			cxld = cxl_endpoint_decoder_alloc(port);
 | 
								cxlsd = cxl_switch_decoder_alloc(port, target_count);
 | 
				
			||||||
		if (IS_ERR(cxld)) {
 | 
								if (IS_ERR(cxlsd)) {
 | 
				
			||||||
			dev_warn(&port->dev,
 | 
									dev_warn(&port->dev,
 | 
				
			||||||
				 "Failed to allocate the decoder\n");
 | 
										 "Failed to allocate the decoder\n");
 | 
				
			||||||
			return PTR_ERR(cxld);
 | 
									return PTR_ERR(cxlsd);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cxld = &cxlsd->cxld;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								struct cxl_endpoint_decoder *cxled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								cxled = cxl_endpoint_decoder_alloc(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (IS_ERR(cxled)) {
 | 
				
			||||||
 | 
									dev_warn(&port->dev,
 | 
				
			||||||
 | 
										 "Failed to allocate the decoder\n");
 | 
				
			||||||
 | 
									return PTR_ERR(cxled);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cxld = &cxled->cxld;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cxld->decoder_range = (struct range) {
 | 
							cxld->hpa_range = (struct range) {
 | 
				
			||||||
			.start = 0,
 | 
								.start = 0,
 | 
				
			||||||
			.end = -1,
 | 
								.end = -1,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cxld->flags = CXL_DECODER_F_ENABLE;
 | 
					 | 
				
			||||||
		cxld->interleave_ways = min_not_zero(target_count, 1);
 | 
							cxld->interleave_ways = min_not_zero(target_count, 1);
 | 
				
			||||||
		cxld->interleave_granularity = SZ_4K;
 | 
							cxld->interleave_granularity = SZ_4K;
 | 
				
			||||||
		cxld->target_type = CXL_DECODER_EXPANDER;
 | 
							cxld->target_type = CXL_DECODER_EXPANDER;
 | 
				
			||||||
 | 
							cxld->commit = mock_decoder_commit;
 | 
				
			||||||
 | 
							cxld->reset = mock_decoder_reset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (target_count) {
 | 
							if (target_count) {
 | 
				
			||||||
			rc = device_for_each_child(port->uport, &ctx,
 | 
								rc = device_for_each_child(port->uport, &ctx,
 | 
				
			||||||
| 
						 | 
					@ -569,44 +627,6 @@ static void mock_companion(struct acpi_device *adev, struct device *dev)
 | 
				
			||||||
#define SZ_512G (SZ_64G * 8)
 | 
					#define SZ_512G (SZ_64G * 8)
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct platform_device *alloc_memdev(int id)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	struct resource res[] = {
 | 
					 | 
				
			||||||
		[0] = {
 | 
					 | 
				
			||||||
			.flags = IORESOURCE_MEM,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		[1] = {
 | 
					 | 
				
			||||||
			.flags = IORESOURCE_MEM,
 | 
					 | 
				
			||||||
			.desc = IORES_DESC_PERSISTENT_MEMORY,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	struct platform_device *pdev;
 | 
					 | 
				
			||||||
	int i, rc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (i = 0; i < ARRAY_SIZE(res); i++) {
 | 
					 | 
				
			||||||
		struct cxl_mock_res *r = alloc_mock_res(SZ_256M);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!r)
 | 
					 | 
				
			||||||
			return NULL;
 | 
					 | 
				
			||||||
		res[i].start = r->range.start;
 | 
					 | 
				
			||||||
		res[i].end = r->range.end;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pdev = platform_device_alloc("cxl_mem", id);
 | 
					 | 
				
			||||||
	if (!pdev)
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rc = platform_device_add_resources(pdev, res, ARRAY_SIZE(res));
 | 
					 | 
				
			||||||
	if (rc)
 | 
					 | 
				
			||||||
		goto err;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return pdev;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
err:
 | 
					 | 
				
			||||||
	platform_device_put(pdev);
 | 
					 | 
				
			||||||
	return NULL;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static __init int cxl_test_init(void)
 | 
					static __init int cxl_test_init(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int rc, i;
 | 
						int rc, i;
 | 
				
			||||||
| 
						 | 
					@ -619,7 +639,8 @@ static __init int cxl_test_init(void)
 | 
				
			||||||
		goto err_gen_pool_create;
 | 
							goto err_gen_pool_create;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc = gen_pool_add(cxl_mock_pool, SZ_512G, SZ_64G, NUMA_NO_NODE);
 | 
						rc = gen_pool_add(cxl_mock_pool, iomem_resource.end + 1 - SZ_64G,
 | 
				
			||||||
 | 
								  SZ_64G, NUMA_NO_NODE);
 | 
				
			||||||
	if (rc)
 | 
						if (rc)
 | 
				
			||||||
		goto err_gen_pool_add;
 | 
							goto err_gen_pool_add;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -708,7 +729,7 @@ static __init int cxl_test_init(void)
 | 
				
			||||||
		struct platform_device *dport = cxl_switch_dport[i];
 | 
							struct platform_device *dport = cxl_switch_dport[i];
 | 
				
			||||||
		struct platform_device *pdev;
 | 
							struct platform_device *pdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pdev = alloc_memdev(i);
 | 
							pdev = platform_device_alloc("cxl_mem", i);
 | 
				
			||||||
		if (!pdev)
 | 
							if (!pdev)
 | 
				
			||||||
			goto err_mem;
 | 
								goto err_mem;
 | 
				
			||||||
		pdev->dev.parent = &dport->dev;
 | 
							pdev->dev.parent = &dport->dev;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@
 | 
				
			||||||
#include <cxlmem.h>
 | 
					#include <cxlmem.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define LSA_SIZE SZ_128K
 | 
					#define LSA_SIZE SZ_128K
 | 
				
			||||||
 | 
					#define DEV_SIZE SZ_2G
 | 
				
			||||||
#define EFFECT(x) (1U << x)
 | 
					#define EFFECT(x) (1U << x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct cxl_cel_entry mock_cel[] = {
 | 
					static struct cxl_cel_entry mock_cel[] = {
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,10 @@ static struct cxl_cel_entry mock_cel[] = {
 | 
				
			||||||
		.opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA),
 | 
							.opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA),
 | 
				
			||||||
		.effect = cpu_to_le16(0),
 | 
							.effect = cpu_to_le16(0),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.opcode = cpu_to_le16(CXL_MBOX_OP_GET_PARTITION_INFO),
 | 
				
			||||||
 | 
							.effect = cpu_to_le16(0),
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		.opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA),
 | 
							.opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA),
 | 
				
			||||||
		.effect = cpu_to_le16(EFFECT(1) | EFFECT(2)),
 | 
							.effect = cpu_to_le16(EFFECT(1) | EFFECT(2)),
 | 
				
			||||||
| 
						 | 
					@ -97,46 +102,41 @@ static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd)
 | 
					static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct platform_device *pdev = to_platform_device(cxlds->dev);
 | 
					 | 
				
			||||||
	struct cxl_mbox_identify id = {
 | 
						struct cxl_mbox_identify id = {
 | 
				
			||||||
		.fw_revision = { "mock fw v1 " },
 | 
							.fw_revision = { "mock fw v1 " },
 | 
				
			||||||
		.lsa_size = cpu_to_le32(LSA_SIZE),
 | 
							.lsa_size = cpu_to_le32(LSA_SIZE),
 | 
				
			||||||
		/* FIXME: Add partition support */
 | 
							.partition_align =
 | 
				
			||||||
		.partition_align = cpu_to_le64(0),
 | 
								cpu_to_le64(SZ_256M / CXL_CAPACITY_MULTIPLIER),
 | 
				
			||||||
 | 
							.total_capacity =
 | 
				
			||||||
 | 
								cpu_to_le64(DEV_SIZE / CXL_CAPACITY_MULTIPLIER),
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	u64 capacity = 0;
 | 
					 | 
				
			||||||
	int i;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (cmd->size_out < sizeof(id))
 | 
						if (cmd->size_out < sizeof(id))
 | 
				
			||||||
		return -EINVAL;
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (i = 0; i < 2; i++) {
 | 
					 | 
				
			||||||
		struct resource *res;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
 | 
					 | 
				
			||||||
		if (!res)
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		capacity += resource_size(res) / CXL_CAPACITY_MULTIPLIER;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (le64_to_cpu(id.partition_align))
 | 
					 | 
				
			||||||
			continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (res->desc == IORES_DESC_PERSISTENT_MEMORY)
 | 
					 | 
				
			||||||
			id.persistent_capacity = cpu_to_le64(
 | 
					 | 
				
			||||||
				resource_size(res) / CXL_CAPACITY_MULTIPLIER);
 | 
					 | 
				
			||||||
		else
 | 
					 | 
				
			||||||
			id.volatile_capacity = cpu_to_le64(
 | 
					 | 
				
			||||||
				resource_size(res) / CXL_CAPACITY_MULTIPLIER);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	id.total_capacity = cpu_to_le64(capacity);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	memcpy(cmd->payload_out, &id, sizeof(id));
 | 
						memcpy(cmd->payload_out, &id, sizeof(id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int mock_partition_info(struct cxl_dev_state *cxlds,
 | 
				
			||||||
 | 
								       struct cxl_mbox_cmd *cmd)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct cxl_mbox_get_partition_info pi = {
 | 
				
			||||||
 | 
							.active_volatile_cap =
 | 
				
			||||||
 | 
								cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER),
 | 
				
			||||||
 | 
							.active_persistent_cap =
 | 
				
			||||||
 | 
								cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER),
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (cmd->size_out < sizeof(pi))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memcpy(cmd->payload_out, &pi, sizeof(pi));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int mock_get_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd)
 | 
					static int mock_get_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in;
 | 
						struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in;
 | 
				
			||||||
| 
						 | 
					@ -221,6 +221,9 @@ static int cxl_mock_mbox_send(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *
 | 
				
			||||||
	case CXL_MBOX_OP_GET_LSA:
 | 
						case CXL_MBOX_OP_GET_LSA:
 | 
				
			||||||
		rc = mock_get_lsa(cxlds, cmd);
 | 
							rc = mock_get_lsa(cxlds, cmd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
						case CXL_MBOX_OP_GET_PARTITION_INFO:
 | 
				
			||||||
 | 
							rc = mock_partition_info(cxlds, cmd);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	case CXL_MBOX_OP_SET_LSA:
 | 
						case CXL_MBOX_OP_SET_LSA:
 | 
				
			||||||
		rc = mock_set_lsa(cxlds, cmd);
 | 
							rc = mock_set_lsa(cxlds, cmd);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					@ -282,7 +285,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev)
 | 
				
			||||||
	if (IS_ERR(cxlmd))
 | 
						if (IS_ERR(cxlmd))
 | 
				
			||||||
		return PTR_ERR(cxlmd);
 | 
							return PTR_ERR(cxlmd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (range_len(&cxlds->pmem_range) && IS_ENABLED(CONFIG_CXL_PMEM))
 | 
						if (resource_size(&cxlds->pmem_res) && IS_ENABLED(CONFIG_CXL_PMEM))
 | 
				
			||||||
		rc = devm_cxl_add_nvdimm(dev, cxlmd);
 | 
							rc = devm_cxl_add_nvdimm(dev, cxlmd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -208,13 +208,15 @@ int __wrap_cxl_await_media_ready(struct cxl_dev_state *cxlds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_NS_GPL(__wrap_cxl_await_media_ready, CXL);
 | 
					EXPORT_SYMBOL_NS_GPL(__wrap_cxl_await_media_ready, CXL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool __wrap_cxl_hdm_decode_init(struct cxl_dev_state *cxlds,
 | 
					int __wrap_cxl_hdm_decode_init(struct cxl_dev_state *cxlds,
 | 
				
			||||||
				struct cxl_hdm *cxlhdm)
 | 
								       struct cxl_hdm *cxlhdm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int rc = 0, index;
 | 
						int rc = 0, index;
 | 
				
			||||||
	struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
 | 
						struct cxl_mock_ops *ops = get_cxl_mock_ops(&index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!ops || !ops->is_mock_dev(cxlds->dev))
 | 
						if (ops && ops->is_mock_dev(cxlds->dev))
 | 
				
			||||||
 | 
							rc = 0;
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
		rc = cxl_hdm_decode_init(cxlds, cxlhdm);
 | 
							rc = cxl_hdm_decode_init(cxlds, cxlhdm);
 | 
				
			||||||
	put_cxl_mock_ops(index);
 | 
						put_cxl_mock_ops(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue