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
|
@ -516,6 +516,7 @@ ForEachMacros:
|
|||
- 'of_property_for_each_string'
|
||||
- 'of_property_for_each_u32'
|
||||
- 'pci_bus_for_each_resource'
|
||||
- 'pci_doe_for_each_off'
|
||||
- 'pcl_for_each_chunk'
|
||||
- 'pcl_for_each_segment'
|
||||
- 'pcm_for_each_format'
|
||||
|
|
|
@ -7,6 +7,7 @@ Description:
|
|||
all descendant memdevs for unbind. Writing '1' to this attribute
|
||||
flushes that work.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/memX/firmware_version
|
||||
Date: December, 2020
|
||||
KernelVersion: v5.12
|
||||
|
@ -16,6 +17,7 @@ Description:
|
|||
Memory Device Output Payload in the CXL-2.0
|
||||
specification.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/memX/ram/size
|
||||
Date: December, 2020
|
||||
KernelVersion: v5.12
|
||||
|
@ -25,6 +27,7 @@ Description:
|
|||
identically named field in the Identify Memory Device Output
|
||||
Payload in the CXL-2.0 specification.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/memX/pmem/size
|
||||
Date: December, 2020
|
||||
KernelVersion: v5.12
|
||||
|
@ -34,6 +37,7 @@ Description:
|
|||
identically named field in the Identify Memory Device Output
|
||||
Payload in the CXL-2.0 specification.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/memX/serial
|
||||
Date: January, 2022
|
||||
KernelVersion: v5.18
|
||||
|
@ -43,6 +47,7 @@ Description:
|
|||
capability. Mandatory for CXL devices, see CXL 2.0 8.1.12.2
|
||||
Memory Device PCIe Capabilities and Extended Capabilities.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/memX/numa_node
|
||||
Date: January, 2022
|
||||
KernelVersion: v5.18
|
||||
|
@ -52,114 +57,334 @@ Description:
|
|||
host PCI device for this memory device, emit the CPU node
|
||||
affinity for this device.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/*/devtype
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
CXL device objects export the devtype attribute which mirrors
|
||||
the same value communicated in the DEVTYPE environment variable
|
||||
for uevents for devices on the "cxl" bus.
|
||||
(RO) CXL device objects export the devtype attribute which
|
||||
mirrors the same value communicated in the DEVTYPE environment
|
||||
variable for uevents for devices on the "cxl" bus.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/*/modalias
|
||||
Date: December, 2021
|
||||
KernelVersion: v5.18
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
CXL device objects export the modalias attribute which mirrors
|
||||
the same value communicated in the MODALIAS environment variable
|
||||
for uevents for devices on the "cxl" bus.
|
||||
(RO) CXL device objects export the modalias attribute which
|
||||
mirrors the same value communicated in the MODALIAS environment
|
||||
variable for uevents for devices on the "cxl" bus.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/portX/uport
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
CXL port objects are enumerated from either a platform firmware
|
||||
device (ACPI0017 and ACPI0016) or PCIe switch upstream port with
|
||||
CXL component registers. The 'uport' symlink connects the CXL
|
||||
portX object to the device that published the CXL port
|
||||
(RO) CXL port objects are enumerated from either a platform
|
||||
firmware device (ACPI0017 and ACPI0016) or PCIe switch upstream
|
||||
port with CXL component registers. The 'uport' symlink connects
|
||||
the CXL portX object to the device that published the CXL port
|
||||
capability.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/portX/dportY
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
CXL port objects are enumerated from either a platform firmware
|
||||
device (ACPI0017 and ACPI0016) or PCIe switch upstream port with
|
||||
CXL component registers. The 'dportY' symlink identifies one or
|
||||
more downstream ports that the upstream port may target in its
|
||||
decode of CXL memory resources. The 'Y' integer reflects the
|
||||
hardware port unique-id used in the hardware decoder target
|
||||
list.
|
||||
(RO) CXL port objects are enumerated from either a platform
|
||||
firmware device (ACPI0017 and ACPI0016) or PCIe switch upstream
|
||||
port with CXL component registers. The 'dportY' symlink
|
||||
identifies one or more downstream ports that the upstream port
|
||||
may target in its decode of CXL memory resources. The 'Y'
|
||||
integer reflects the hardware port unique-id used in the
|
||||
hardware decoder target list.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
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
|
||||
PCIe device (see CXL 2.0 section 8.2.5.12 CXL HDM Decoder
|
||||
Capability Structure). The 'X' in decoderX.Y represents the
|
||||
cxl_port container of this decoder, and 'Y' represents the
|
||||
instance id of a given decoder resource.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y/{start,size}
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
The 'start' and 'size' attributes together convey the physical
|
||||
address base and number of bytes mapped in the decoder's decode
|
||||
window. For decoders of devtype "cxl_decoder_root" the address
|
||||
range is fixed. For decoders of devtype "cxl_decoder_switch" the
|
||||
address is bounded by the decode range of the cxl_port ancestor
|
||||
of the decoder's cxl_port, and dynamically updates based on the
|
||||
active memory regions in that address space.
|
||||
(RO) The 'start' and 'size' attributes together convey the
|
||||
physical address base and number of bytes mapped in the
|
||||
decoder's decode window. For decoders of devtype
|
||||
"cxl_decoder_root" the address range is fixed. For decoders of
|
||||
devtype "cxl_decoder_switch" the address is bounded by the
|
||||
decode range of the cxl_port ancestor of the decoder's cxl_port,
|
||||
and dynamically updates based on the active memory regions in
|
||||
that address space.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y/locked
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
CXL HDM decoders have the capability to lock the configuration
|
||||
until the next device reset. For decoders of devtype
|
||||
"cxl_decoder_root" there is no standard facility to unlock them.
|
||||
For decoders of devtype "cxl_decoder_switch" a secondary bus
|
||||
reset, of the PCIe bridge that provides the bus for this
|
||||
decoders uport, unlocks / resets the decoder.
|
||||
(RO) CXL HDM decoders have the capability to lock the
|
||||
configuration until the next device reset. For decoders of
|
||||
devtype "cxl_decoder_root" there is no standard facility to
|
||||
unlock them. For decoders of devtype "cxl_decoder_switch" a
|
||||
secondary bus reset, of the PCIe bridge that provides the bus
|
||||
for this decoders uport, unlocks / resets the decoder.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y/target_list
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
Display a comma separated list of the current decoder target
|
||||
configuration. The list is ordered by the current configured
|
||||
interleave order of the decoder's dport instances. Each entry in
|
||||
the list is a dport id.
|
||||
(RO) Display a comma separated list of the current decoder
|
||||
target configuration. The list is ordered by the current
|
||||
configured interleave order of the decoder's dport instances.
|
||||
Each entry in the list is a dport id.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y/cap_{pmem,ram,type2,type3}
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
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
|
||||
firmware. A fixed window may only support a subset of memory
|
||||
types. The 'cap_*' attributes indicate whether persistent
|
||||
memory, volatile memory, accelerator memory, and / or expander
|
||||
memory may be mapped behind this decoder's memory window.
|
||||
|
||||
|
||||
What: /sys/bus/cxl/devices/decoderX.Y/target_type
|
||||
Date: June, 2021
|
||||
KernelVersion: v5.14
|
||||
Contact: linux-cxl@vger.kernel.org
|
||||
Description:
|
||||
When a CXL decoder is of devtype "cxl_decoder_switch", it can
|
||||
optionally decode either accelerator memory (type-2) or expander
|
||||
memory (type-3). The 'target_type' attribute indicates the
|
||||
current setting which may dynamically change based on what
|
||||
(RO) When a CXL decoder is of devtype "cxl_decoder_switch", it
|
||||
can optionally decode either accelerator memory (type-2) or
|
||||
expander memory (type-3). The 'target_type' attribute indicates
|
||||
the current setting which may dynamically change based on what
|
||||
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
|
||||
: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
|
||||
===================
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ int memory_add_physaddr_to_nid(u64 start)
|
|||
{
|
||||
return hot_add_scn_to_nid(start);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(memory_add_physaddr_to_nid);
|
||||
#endif
|
||||
|
||||
int __weak create_section_mapping(unsigned long start, unsigned long end,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
menuconfig CXL_BUS
|
||||
tristate "CXL (Compute Express Link) Devices Support"
|
||||
depends on PCI
|
||||
select PCI_DOE
|
||||
help
|
||||
CXL is a bus that is electrically compatible with PCI Express, but
|
||||
layers three protocols on that signalling (CXL.io, CXL.cache, and
|
||||
|
@ -102,4 +103,12 @@ config CXL_SUSPEND
|
|||
def_bool y
|
||||
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
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
#include "cxlpci.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)
|
||||
{
|
||||
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,
|
||||
struct acpi_cedt_cfmws *cfmws)
|
||||
{
|
||||
int expected_len;
|
||||
int rc, expected_len;
|
||||
unsigned int ways;
|
||||
|
||||
if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
|
||||
dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
|
||||
|
@ -51,14 +48,14 @@ static int cxl_acpi_cfmws_verify(struct device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (CFMWS_INTERLEAVE_WAYS(cfmws) > CXL_DECODER_MAX_INTERLEAVE) {
|
||||
dev_err(dev, "CFMWS Interleave Ways (%d) too large\n",
|
||||
CFMWS_INTERLEAVE_WAYS(cfmws));
|
||||
rc = cxl_to_ways(cfmws->interleave_ways, &ways);
|
||||
if (rc) {
|
||||
dev_err(dev, "CFMWS Interleave Ways (%d) invalid\n",
|
||||
cfmws->interleave_ways);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
expected_len = struct_size((cfmws), interleave_targets,
|
||||
CFMWS_INTERLEAVE_WAYS(cfmws));
|
||||
expected_len = struct_size(cfmws, interleave_targets, ways);
|
||||
|
||||
if (cfmws->header.length < expected_len) {
|
||||
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 device *dev;
|
||||
struct cxl_port *root_port;
|
||||
struct resource *cxl_res;
|
||||
int id;
|
||||
};
|
||||
|
||||
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];
|
||||
struct cxl_cfmws_context *ctx = arg;
|
||||
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 acpi_cedt_cfmws *cfmws;
|
||||
struct cxl_decoder *cxld;
|
||||
int rc, i;
|
||||
unsigned int ways, i, ig;
|
||||
struct resource *res;
|
||||
int rc;
|
||||
|
||||
cfmws = (struct acpi_cedt_cfmws *) header;
|
||||
|
||||
|
@ -99,19 +102,51 @@ static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
|
|||
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];
|
||||
|
||||
cxld = cxl_root_decoder_alloc(root_port, CFMWS_INTERLEAVE_WAYS(cfmws));
|
||||
if (IS_ERR(cxld))
|
||||
res = kzalloc(sizeof(*res), GFP_KERNEL);
|
||||
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;
|
||||
|
||||
cxld = &cxlrd->cxlsd.cxld;
|
||||
cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
|
||||
cxld->target_type = CXL_DECODER_EXPANDER;
|
||||
cxld->platform_res = (struct resource)DEFINE_RES_MEM(cfmws->base_hpa,
|
||||
cfmws->window_size);
|
||||
cxld->interleave_ways = CFMWS_INTERLEAVE_WAYS(cfmws);
|
||||
cxld->interleave_granularity = CFMWS_INTERLEAVE_GRANULARITY(cfmws);
|
||||
cxld->hpa_range = (struct range) {
|
||||
.start = res->start,
|
||||
.end = res->end,
|
||||
};
|
||||
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);
|
||||
if (rc)
|
||||
|
@ -119,15 +154,22 @@ static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
|
|||
else
|
||||
rc = cxl_decoder_autoremove(dev, cxld);
|
||||
if (rc) {
|
||||
dev_err(dev, "Failed to add decoder for %pr\n",
|
||||
&cxld->platform_res);
|
||||
dev_err(dev, "Failed to add decode range [%#llx - %#llx]\n",
|
||||
cxld->hpa_range.start, cxld->hpa_range.end);
|
||||
return 0;
|
||||
}
|
||||
dev_dbg(dev, "add: %s node: %d range %pr\n", dev_name(&cxld->dev),
|
||||
phys_to_target_node(cxld->platform_res.start),
|
||||
&cxld->platform_res);
|
||||
dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n",
|
||||
dev_name(&cxld->dev),
|
||||
phys_to_target_node(cxld->hpa_range.start),
|
||||
cxld->hpa_range.start, cxld->hpa_range.end);
|
||||
|
||||
return 0;
|
||||
|
||||
err_insert:
|
||||
kfree(res->name);
|
||||
err_name:
|
||||
kfree(res);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
__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)
|
||||
return rc;
|
||||
|
||||
port = devm_cxl_add_port(host, match, dport->component_reg_phys,
|
||||
root_port);
|
||||
port = devm_cxl_add_port(host, match, dport->component_reg_phys, dport);
|
||||
if (IS_ERR(port))
|
||||
return PTR_ERR(port);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int rc;
|
||||
struct resource *cxl_res;
|
||||
struct cxl_port *root_port;
|
||||
struct device *host = &pdev->dev;
|
||||
struct acpi_device *adev = ACPI_COMPANION(host);
|
||||
|
@ -296,6 +455,14 @@ static int cxl_acpi_probe(struct platform_device *pdev)
|
|||
if (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);
|
||||
if (IS_ERR(root_port))
|
||||
return PTR_ERR(root_port);
|
||||
|
@ -306,11 +473,28 @@ static int cxl_acpi_probe(struct platform_device *pdev)
|
|||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = devm_add_action_or_reset(host, remove_cxl_resources, cxl_res);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
ctx = (struct cxl_cfmws_context) {
|
||||
.dev = host,
|
||||
.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
|
||||
|
@ -337,12 +521,19 @@ static const struct acpi_device_id 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 = {
|
||||
.probe = cxl_acpi_probe,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.acpi_match_table = cxl_acpi_ids,
|
||||
},
|
||||
.id_table = cxl_test_ids,
|
||||
};
|
||||
|
||||
module_platform_driver(cxl_acpi_driver);
|
||||
|
|
|
@ -10,3 +10,4 @@ cxl_core-y += memdev.o
|
|||
cxl_core-y += mbox.o
|
||||
cxl_core-y += pci.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;
|
||||
|
||||
#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_mem_query_commands;
|
||||
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,
|
||||
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);
|
||||
void cxl_memdev_exit(void);
|
||||
void cxl_mbox_init(void);
|
||||
void cxl_mbox_exit(void);
|
||||
|
||||
#endif /* __CXL_CORE_H__ */
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
|
||||
#include <linux/io-64-nonatomic-hi-lo.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
|
@ -16,6 +17,8 @@
|
|||
* for enumerating these registers and capabilities.
|
||||
*/
|
||||
|
||||
DECLARE_RWSEM(cxl_dpa_rwsem);
|
||||
|
||||
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
|
||||
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)
|
||||
{
|
||||
struct cxl_decoder *cxld;
|
||||
struct cxl_dport *dport;
|
||||
struct cxl_switch_decoder *cxlsd;
|
||||
struct cxl_dport *dport = NULL;
|
||||
int single_port_map[1];
|
||||
unsigned long index;
|
||||
|
||||
cxld = cxl_switch_decoder_alloc(port, 1);
|
||||
if (IS_ERR(cxld))
|
||||
return PTR_ERR(cxld);
|
||||
cxlsd = cxl_switch_decoder_alloc(port, 1);
|
||||
if (IS_ERR(cxlsd))
|
||||
return PTR_ERR(cxlsd);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -124,47 +129,577 @@ struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port)
|
|||
return ERR_PTR(-ENXIO);
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, cxlhdm);
|
||||
|
||||
return cxlhdm;
|
||||
}
|
||||
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) {
|
||||
case 0 ... 4:
|
||||
return 1 << val;
|
||||
case 8 ... 10:
|
||||
return 3 << (val - 8);
|
||||
default:
|
||||
return 0;
|
||||
down_read(&cxl_dpa_rwsem);
|
||||
for (p1 = cxlds->dpa_res.child; p1; p1 = p1->sibling) {
|
||||
__cxl_dpa_debug(file, p1, 0);
|
||||
for (p2 = p1->child; p2; p2 = p2->sibling)
|
||||
__cxl_dpa_debug(file, p2, 1);
|
||||
}
|
||||
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,
|
||||
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;
|
||||
int i;
|
||||
union {
|
||||
u64 value;
|
||||
unsigned char target_id[8];
|
||||
} target_list;
|
||||
|
||||
if (is_endpoint_decoder(&cxld->dev))
|
||||
cxled = to_cxl_endpoint_decoder(&cxld->dev);
|
||||
|
||||
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_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));
|
||||
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;
|
||||
if (base == U64_MAX || size == U64_MAX) {
|
||||
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;
|
||||
}
|
||||
|
||||
cxld->decoder_range = (struct range) {
|
||||
cxld->hpa_range = (struct range) {
|
||||
.start = base,
|
||||
.end = base + size - 1,
|
||||
};
|
||||
|
||||
/* switch decoders are always enabled if committed */
|
||||
if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
|
||||
/* decoders are enabled if committed */
|
||||
if (committed) {
|
||||
cxld->flags |= CXL_DECODER_F_ENABLE;
|
||||
if (ctrl & CXL_HDM_DECODER0_CTRL_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);
|
||||
if (!cxld->interleave_ways) {
|
||||
rc = cxl_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
|
||||
&cxld->interleave_ways);
|
||||
if (rc) {
|
||||
dev_warn(&port->dev,
|
||||
"decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
|
||||
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))
|
||||
cxld->target_type = CXL_DECODER_EXPANDER;
|
||||
else
|
||||
cxld->target_type = CXL_DECODER_ACCELERATOR;
|
||||
if (!cxled) {
|
||||
target_list.value =
|
||||
ioread64_hi_lo(hdm + CXL_HDM_DECODER0_TL_LOW(which));
|
||||
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;
|
||||
|
||||
target_list.value =
|
||||
ioread64_hi_lo(hdm + CXL_HDM_DECODER0_TL_LOW(which));
|
||||
for (i = 0; i < cxld->interleave_ways; i++)
|
||||
target_map[i] = target_list.target_id[i];
|
||||
|
||||
dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
|
||||
if (remainder) {
|
||||
dev_err(&port->dev,
|
||||
"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;
|
||||
}
|
||||
|
||||
|
@ -216,7 +789,8 @@ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
|
|||
{
|
||||
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
|
||||
struct cxl_port *port = cxlhdm->port;
|
||||
int i, committed, failed;
|
||||
int i, committed;
|
||||
u64 dpa_base = 0;
|
||||
u32 ctrl;
|
||||
|
||||
/*
|
||||
|
@ -236,27 +810,37 @@ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
|
|||
if (committed != cxlhdm->decoder_count)
|
||||
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 rc, target_count = cxlhdm->target_count;
|
||||
struct cxl_decoder *cxld;
|
||||
|
||||
if (is_cxl_endpoint(port))
|
||||
cxld = cxl_endpoint_decoder_alloc(port);
|
||||
else
|
||||
cxld = cxl_switch_decoder_alloc(port, target_count);
|
||||
if (IS_ERR(cxld)) {
|
||||
dev_warn(&port->dev,
|
||||
"Failed to allocate the decoder\n");
|
||||
return PTR_ERR(cxld);
|
||||
if (is_cxl_endpoint(port)) {
|
||||
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;
|
||||
} 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,
|
||||
cxlhdm->regs.hdm_decoder, i);
|
||||
rc = init_hdm_decoder(port, cxld, target_map, hdm, i, &dpa_base);
|
||||
if (rc) {
|
||||
put_device(&cxld->dev);
|
||||
failed++;
|
||||
continue;
|
||||
return rc;
|
||||
}
|
||||
rc = add_hdm_decoder(port, cxld, target_map);
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
struct cxl_mbox_get_partition_info {
|
||||
__le64 active_volatile_cap;
|
||||
__le64 active_persistent_cap;
|
||||
__le64 next_volatile_cap;
|
||||
__le64 next_persistent_cap;
|
||||
} __packed pi;
|
||||
struct cxl_mbox_get_partition_info pi;
|
||||
int rc;
|
||||
|
||||
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 =
|
||||
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);
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
if (cxlds->partition_align_bytes == 0) {
|
||||
cxlds->ram_range.start = 0;
|
||||
cxlds->ram_range.end = cxlds->volatile_only_bytes - 1;
|
||||
cxlds->pmem_range.start = cxlds->volatile_only_bytes;
|
||||
cxlds->pmem_range.end = cxlds->volatile_only_bytes +
|
||||
cxlds->persistent_only_bytes - 1;
|
||||
res->name = type;
|
||||
res->start = start;
|
||||
res->end = start + size - 1;
|
||||
res->flags = IORESOURCE_MEM;
|
||||
if (resource_size(res) == 0) {
|
||||
dev_dbg(dev, "DPA(%s): no capacity\n", res->name);
|
||||
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);
|
||||
if (rc) {
|
||||
dev_err(cxlds->dev, "Failed to query partition information\n");
|
||||
dev_err(dev, "Failed to query partition information\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
dev_dbg(cxlds->dev,
|
||||
"Get Partition Info\n"
|
||||
" active_volatile_bytes = %#llx\n"
|
||||
" active_persistent_bytes = %#llx\n"
|
||||
" next_volatile_bytes = %#llx\n"
|
||||
" next_persistent_bytes = %#llx\n",
|
||||
cxlds->active_volatile_bytes, cxlds->active_persistent_bytes,
|
||||
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;
|
||||
rc = add_dpa_res(dev, &cxlds->dpa_res, &cxlds->ram_res, 0,
|
||||
cxlds->active_volatile_bytes, "ram");
|
||||
if (rc)
|
||||
return rc;
|
||||
return add_dpa_res(dev, &cxlds->dpa_res, &cxlds->pmem_res,
|
||||
cxlds->active_volatile_bytes,
|
||||
cxlds->active_persistent_bytes, "pmem");
|
||||
}
|
||||
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);
|
||||
|
||||
static struct dentry *cxl_debugfs;
|
||||
|
||||
void __init cxl_mbox_init(void)
|
||||
{
|
||||
struct dentry *mbox_debugfs;
|
||||
|
||||
cxl_debugfs = debugfs_create_dir("cxl", NULL);
|
||||
mbox_debugfs = debugfs_create_dir("mbox", cxl_debugfs);
|
||||
mbox_debugfs = cxl_debugfs_create_dir("mbox");
|
||||
debugfs_create_bool("raw_allow_all", 0600, mbox_debugfs,
|
||||
&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_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);
|
||||
}
|
||||
|
@ -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_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);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-doe.h>
|
||||
#include <cxlpci.h>
|
||||
#include <cxlmem.h>
|
||||
#include <cxl.h>
|
||||
|
@ -225,7 +226,6 @@ static int dvsec_range_allowed(struct device *dev, void *arg)
|
|||
{
|
||||
struct range *dev_range = arg;
|
||||
struct cxl_decoder *cxld;
|
||||
struct range root_range;
|
||||
|
||||
if (!is_root_decoder(dev))
|
||||
return 0;
|
||||
|
@ -237,12 +237,7 @@ static int dvsec_range_allowed(struct device *dev, void *arg)
|
|||
if (!(cxld->flags & CXL_DECODER_F_RAM))
|
||||
return 0;
|
||||
|
||||
root_range = (struct range) {
|
||||
.start = cxld->platform_res.start,
|
||||
.end = cxld->platform_res.end,
|
||||
};
|
||||
|
||||
return range_contains(&root_range, dev_range);
|
||||
return range_contains(&cxld->hpa_range, dev_range);
|
||||
}
|
||||
|
||||
static void disable_hdm(void *_cxlhdm)
|
||||
|
@ -458,3 +453,175 @@ hdm_init:
|
|||
return 0;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!port)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@
|
|||
#include <linux/libnvdimm.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
/**
|
||||
|
@ -53,9 +54,12 @@
|
|||
#define CXL_HDM_DECODER0_CTRL_LOCK BIT(8)
|
||||
#define CXL_HDM_DECODER0_CTRL_COMMIT BIT(9)
|
||||
#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_TL_LOW(i) (0x20 * (i) + 0x24)
|
||||
#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)
|
||||
{
|
||||
|
@ -64,6 +68,57 @@ static inline int cxl_hdm_decoder_count(u32 cap_hdr)
|
|||
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 */
|
||||
#define CXLDEV_CAP_ARRAY_OFFSET 0x0
|
||||
#define CXLDEV_CAP_ARRAY_CAP_ID 0
|
||||
|
@ -193,37 +248,153 @@ enum cxl_decoder_type {
|
|||
*/
|
||||
#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
|
||||
* @id: kernel device name id
|
||||
* @platform_res: address space resources considered by root decoder
|
||||
* @decoder_range: address space resources considered by midlevel decoder
|
||||
* @hpa_range: Host physical address range mapped by this decoder
|
||||
* @interleave_ways: number of cxl_dports in this decode
|
||||
* @interleave_granularity: data stride per dport
|
||||
* @target_type: accelerator vs expander (type2 vs type3) selector
|
||||
* @region: currently assigned region for this decoder
|
||||
* @flags: memory type capabilities and locking
|
||||
* @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
|
||||
*/
|
||||
* @commit: device/decoder-type specific callback to commit settings to hw
|
||||
* @reset: device/decoder-type specific callback to reset hw settings
|
||||
*/
|
||||
struct cxl_decoder {
|
||||
struct device dev;
|
||||
int id;
|
||||
union {
|
||||
struct resource platform_res;
|
||||
struct range decoder_range;
|
||||
};
|
||||
struct range hpa_range;
|
||||
int interleave_ways;
|
||||
int interleave_granularity;
|
||||
enum cxl_decoder_type target_type;
|
||||
struct cxl_region *region;
|
||||
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;
|
||||
int nr_targets;
|
||||
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
|
||||
* @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 device dev;
|
||||
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.
|
||||
* @dev: this port's device
|
||||
* @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
|
||||
* @dports: cxl_dport instances referenced by decoders
|
||||
* @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
|
||||
* @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)
|
||||
* @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.
|
||||
* @cdat: Cached CDAT data
|
||||
* @cdat_available: Should a CDAT attribute be available in sysfs
|
||||
*/
|
||||
struct cxl_port {
|
||||
struct device dev;
|
||||
struct device *uport;
|
||||
struct device *host_bridge;
|
||||
int id;
|
||||
struct list_head dports;
|
||||
struct list_head endpoints;
|
||||
struct xarray dports;
|
||||
struct xarray endpoints;
|
||||
struct xarray regions;
|
||||
struct cxl_dport *parent_dport;
|
||||
struct ida decoder_ida;
|
||||
int hdm_end;
|
||||
int commit_end;
|
||||
resource_size_t component_reg_phys;
|
||||
bool dead;
|
||||
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
|
||||
* @dport: PCI bridge or firmware device representing the downstream link
|
||||
* @port_id: unique hardware identifier for dport in decoder target list
|
||||
* @component_reg_phys: downstream port component registers
|
||||
* @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 device *dport;
|
||||
int port_id;
|
||||
resource_size_t component_reg_phys;
|
||||
struct cxl_port *port;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cxl_ep - track an endpoint's interest in a port
|
||||
* @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 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 cxl_port *devm_cxl_add_port(struct device *host, struct device *uport,
|
||||
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);
|
||||
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
|
||||
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);
|
||||
|
||||
struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port,
|
||||
struct device *dport, int port_id,
|
||||
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_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_endpoint_decoder(struct device *dev);
|
||||
bool is_cxl_decoder(struct device *dev);
|
||||
struct cxl_decoder *cxl_root_decoder_alloc(struct cxl_port *port,
|
||||
unsigned int nr_targets);
|
||||
struct cxl_decoder *cxl_switch_decoder_alloc(struct cxl_port *port,
|
||||
unsigned int nr_targets);
|
||||
struct cxl_root_decoder *cxl_root_decoder_alloc(struct cxl_port *port,
|
||||
unsigned int nr_targets);
|
||||
struct cxl_switch_decoder *cxl_switch_decoder_alloc(struct cxl_port *port,
|
||||
unsigned int nr_targets);
|
||||
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_autoremove(struct device *host, struct cxl_decoder *cxld);
|
||||
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_add_passthrough_decoder(struct cxl_port *port);
|
||||
|
||||
bool is_cxl_region(struct device *dev);
|
||||
|
||||
extern struct bus_type cxl_bus_type;
|
||||
|
||||
struct cxl_driver {
|
||||
|
@ -385,6 +623,8 @@ void cxl_driver_unregister(struct cxl_driver *cxl_drv);
|
|||
#define CXL_DEVICE_PORT 3
|
||||
#define CXL_DEVICE_ROOT 4
|
||||
#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 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_bridge(struct device *dev);
|
||||
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
|
||||
|
|
|
@ -50,6 +50,24 @@ static inline struct cxl_memdev *to_cxl_memdev(struct device *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);
|
||||
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.
|
||||
* @enabled_cmds: Hardware commands found enabled in CEL.
|
||||
* @exclusive_cmds: Commands that are kernel-internal only
|
||||
* @pmem_range: Active Persistent memory capacity configuration
|
||||
* @ram_range: Active Volatile memory capacity configuration
|
||||
* @dpa_res: Overall DPA resource tree for the device
|
||||
* @pmem_res: Active Persistent memory capacity configuration
|
||||
* @ram_res: Active Volatile memory capacity configuration
|
||||
* @total_bytes: sum of all possible capacities
|
||||
* @volatile_only_bytes: hard volatile capacity
|
||||
* @persistent_only_bytes: hard persistent capacity
|
||||
|
@ -191,6 +210,7 @@ struct cxl_endpoint_dvsec_info {
|
|||
* @component_reg_phys: register base of component registers
|
||||
* @info: Cached DVSEC information about the device.
|
||||
* @serial: PCIe Device Serial Number
|
||||
* @doe_mbs: PCI DOE mailbox array
|
||||
* @mbox_send: @dev specific transport for transmitting mailbox commands
|
||||
*
|
||||
* 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(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX);
|
||||
|
||||
struct range pmem_range;
|
||||
struct range ram_range;
|
||||
struct resource dpa_res;
|
||||
struct resource pmem_res;
|
||||
struct resource ram_res;
|
||||
u64 total_bytes;
|
||||
u64 volatile_only_bytes;
|
||||
u64 persistent_only_bytes;
|
||||
|
@ -224,6 +245,8 @@ struct cxl_dev_state {
|
|||
resource_size_t component_reg_phys;
|
||||
u64 serial;
|
||||
|
||||
struct xarray doe_mbs;
|
||||
|
||||
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;
|
||||
} __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 {
|
||||
__le32 offset;
|
||||
__le32 length;
|
||||
|
@ -370,4 +400,8 @@ struct cxl_hdm {
|
|||
unsigned int interleave_mask;
|
||||
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__ */
|
||||
|
|
|
@ -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);
|
||||
struct cxl_dev_state;
|
||||
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__ */
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
|
@ -24,42 +25,32 @@
|
|||
* 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
|
||||
struct cxl_port *parent_port;
|
||||
struct cxl_dport *dport;
|
||||
struct dentry *dentry;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
|
@ -73,11 +64,17 @@ static int cxl_mem_probe(struct device *dev)
|
|||
if (work_pending(&cxlmd->detach_work))
|
||||
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);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
parent_port = cxl_mem_find_port(cxlmd);
|
||||
parent_port = cxl_mem_find_port(cxlmd, &dport);
|
||||
if (!parent_port) {
|
||||
dev_err(dev, "CXL port topology not found\n");
|
||||
return -ENXIO;
|
||||
|
@ -91,7 +88,7 @@ static int cxl_mem_probe(struct device *dev)
|
|||
goto unlock;
|
||||
}
|
||||
|
||||
rc = create_endpoint(cxlmd, parent_port);
|
||||
rc = devm_cxl_add_endpoint(cxlmd, dport);
|
||||
unlock:
|
||||
device_unlock(&parent_port->dev);
|
||||
put_device(&parent_port->dev);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <linux/mutex.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-doe.h>
|
||||
#include <linux/io.h>
|
||||
#include "cxlmem.h"
|
||||
#include "cxlpci.h"
|
||||
|
@ -386,6 +387,47 @@ static int cxl_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
|
|||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
devm_cxl_pci_create_doe(cxlds);
|
||||
|
||||
rc = cxl_pci_setup_mailbox(cxlds);
|
||||
if (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))
|
||||
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);
|
||||
|
||||
return rc;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <linux/ndctl.h>
|
||||
#include <linux/async.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/nd.h>
|
||||
#include "cxlmem.h"
|
||||
#include "cxl.h"
|
||||
|
||||
|
@ -26,7 +27,23 @@ static void clear_exclusive(void *cxlds)
|
|||
|
||||
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);
|
||||
cxl_nvd->bridge = NULL;
|
||||
}
|
||||
|
||||
static int cxl_nvdimm_probe(struct device *dev)
|
||||
|
@ -39,7 +56,7 @@ static int cxl_nvdimm_probe(struct device *dev)
|
|||
struct nvdimm *nvdimm;
|
||||
int rc;
|
||||
|
||||
cxl_nvb = cxl_find_nvdimm_bridge(cxl_nvd);
|
||||
cxl_nvb = cxl_find_nvdimm_bridge(dev);
|
||||
if (!cxl_nvb)
|
||||
return -ENXIO;
|
||||
|
||||
|
@ -66,6 +83,7 @@ static int cxl_nvdimm_probe(struct device *dev)
|
|||
}
|
||||
|
||||
dev_set_drvdata(dev, nvdimm);
|
||||
cxl_nvd->bridge = cxl_nvb;
|
||||
rc = devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
|
||||
out:
|
||||
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;
|
||||
}
|
||||
|
||||
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))
|
||||
return 0;
|
||||
|
||||
cxl_nvd = to_cxl_nvdimm(dev);
|
||||
if (cxl_nvd->bridge != cxl_nvb)
|
||||
return 0;
|
||||
|
||||
device_release_driver(dev);
|
||||
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)
|
||||
return;
|
||||
|
@ -222,7 +263,10 @@ static void offline_nvdimm_bus(struct nvdimm_bus *nvdimm_bus)
|
|||
* nvdimm_bus_unregister() rips the nvdimm objects out from
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
@ -260,7 +304,7 @@ static void cxl_nvb_update_state(struct work_struct *work)
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -315,6 +359,203 @@ static struct cxl_driver cxl_nvdimm_bridge_driver = {
|
|||
.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
|
||||
* ->state_work referring to the now destroyed cxl_pmem_wq.
|
||||
|
@ -359,8 +600,14 @@ static __init int cxl_pmem_init(void)
|
|||
if (rc)
|
||||
goto err_nvdimm;
|
||||
|
||||
rc = cxl_driver_register(&cxl_pmem_region_driver);
|
||||
if (rc)
|
||||
goto err_region;
|
||||
|
||||
return 0;
|
||||
|
||||
err_region:
|
||||
cxl_driver_unregister(&cxl_nvdimm_driver);
|
||||
err_nvdimm:
|
||||
cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
|
||||
err_bridge:
|
||||
|
@ -370,6 +617,7 @@ err_bridge:
|
|||
|
||||
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_bridge_driver);
|
||||
destroy_cxl_pmem_wq();
|
||||
|
@ -381,3 +629,4 @@ module_exit(cxl_pmem_exit);
|
|||
MODULE_IMPORT_NS(CXL);
|
||||
MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
|
||||
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_dev_state *cxlds = cxlmd->cxlds;
|
||||
|
||||
/* Cache the data early to ensure is_visible() works */
|
||||
read_cdat_data(port);
|
||||
|
||||
get_device(&cxlmd->dev);
|
||||
rc = devm_add_action_or_reset(dev, schedule_detach, cxlmd);
|
||||
if (rc)
|
||||
|
@ -78,10 +81,60 @@ static int cxl_port_probe(struct device *dev)
|
|||
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 = {
|
||||
.name = "cxl_port",
|
||||
.probe = cxl_port_probe,
|
||||
.id = CXL_DEVICE_PORT,
|
||||
.drv = {
|
||||
.dev_groups = cxl_port_attribute_groups,
|
||||
},
|
||||
};
|
||||
|
||||
module_cxl_driver(cxl_port_driver);
|
||||
|
|
|
@ -133,7 +133,8 @@ static void nd_region_release(struct device *dev)
|
|||
put_device(&nvdimm->dev);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -982,9 +983,14 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus,
|
|||
|
||||
if (!nd_region)
|
||||
return NULL;
|
||||
nd_region->id = memregion_alloc(GFP_KERNEL);
|
||||
if (nd_region->id < 0)
|
||||
goto err_id;
|
||||
/* CXL pre-assigns memregion ids before creating nvdimm regions */
|
||||
if (test_bit(ND_REGION_CXL, &ndr_desc->flags)) {
|
||||
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);
|
||||
if (!nd_region->lane)
|
||||
|
@ -1043,9 +1049,10 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus,
|
|||
|
||||
return nd_region;
|
||||
|
||||
err_percpu:
|
||||
memregion_free(nd_region->id);
|
||||
err_id:
|
||||
err_percpu:
|
||||
if (!test_bit(ND_REGION_CXL, &ndr_desc->flags))
|
||||
memregion_free(nd_region->id);
|
||||
err_id:
|
||||
kfree(nd_region);
|
||||
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);
|
||||
|
||||
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 rc = 0;
|
||||
|
|
|
@ -121,6 +121,9 @@ config XEN_PCIDEV_FRONTEND
|
|||
config PCI_ATS
|
||||
bool
|
||||
|
||||
config PCI_DOE
|
||||
bool
|
||||
|
||||
config PCI_ECAM
|
||||
bool
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ obj-$(CONFIG_PCI_ECAM) += ecam.o
|
|||
obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
|
||||
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
|
||||
obj-$(CONFIG_VGA_ARB) += vgaarb.o
|
||||
obj-$(CONFIG_PCI_DOE) += doe.o
|
||||
|
||||
# Endpoint library must be initialized before its users
|
||||
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
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,
|
||||
|
|
|
@ -141,6 +141,7 @@ enum {
|
|||
IORES_DESC_DEVICE_PRIVATE_MEMORY = 6,
|
||||
IORES_DESC_RESERVED = 7,
|
||||
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 *request_free_mem_region(struct resource *base,
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -59,6 +59,9 @@ enum {
|
|||
/* Platform provides asynchronous flush mechanism */
|
||||
ND_REGION_ASYNC = 3,
|
||||
|
||||
/* Region was created by CXL subsystem */
|
||||
ND_REGION_CXL = 4,
|
||||
|
||||
/* mark newly adjusted resources as requiring a label update */
|
||||
DPA_RESOURCE_ADJUSTED = 1 << 0,
|
||||
};
|
||||
|
@ -122,6 +125,7 @@ struct nd_region_desc {
|
|||
int numa_node;
|
||||
int target_node;
|
||||
unsigned long flags;
|
||||
int memregion;
|
||||
struct device_node *of_node;
|
||||
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);
|
||||
}
|
||||
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_bus_desc(int cmd);
|
||||
|
|
|
@ -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
|
||||
|
||||
/* Vendors and devices. Sort key: vendor first, device next. */
|
||||
#define PCI_VENDOR_ID_PCI_SIG 0x0001
|
||||
|
||||
#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) \
|
||||
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 {
|
||||
ssize_t (*show)(struct kobject *, struct attribute *, char *);
|
||||
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_DLF 0x25 /* Data Link Feature */
|
||||
#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_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_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 */
|
||||
|
|
|
@ -489,8 +489,9 @@ int __weak page_is_ram(unsigned long pfn)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(page_is_ram);
|
||||
|
||||
static int __region_intersects(resource_size_t start, size_t size,
|
||||
unsigned long flags, unsigned long desc)
|
||||
static int __region_intersects(struct resource *parent, resource_size_t start,
|
||||
size_t size, unsigned long flags,
|
||||
unsigned long desc)
|
||||
{
|
||||
struct resource res;
|
||||
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.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) &&
|
||||
((desc == IORES_DESC_NONE) ||
|
||||
(desc == p->desc)));
|
||||
|
@ -543,7 +544,7 @@ int region_intersects(resource_size_t start, size_t size, unsigned long flags,
|
|||
int ret;
|
||||
|
||||
read_lock(&resource_lock);
|
||||
ret = __region_intersects(start, size, flags, desc);
|
||||
ret = __region_intersects(&iomem_resource, start, size, flags, desc);
|
||||
read_unlock(&resource_lock);
|
||||
|
||||
return ret;
|
||||
|
@ -891,6 +892,13 @@ void insert_resource_expand_to_fit(struct resource *root, struct resource *new)
|
|||
}
|
||||
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
|
||||
|
@ -1773,62 +1781,139 @@ void resource_list_free(struct list_head *head)
|
|||
}
|
||||
EXPORT_SYMBOL(resource_list_free);
|
||||
|
||||
#ifdef CONFIG_DEVICE_PRIVATE
|
||||
static struct resource *__request_free_mem_region(struct device *dev,
|
||||
struct resource *base, unsigned long size, const char *name)
|
||||
#ifdef CONFIG_GET_FREE_REGION
|
||||
#define GFR_DESCENDING (1UL << 0)
|
||||
#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 region_devres *dr = NULL;
|
||||
|
||||
size = ALIGN(size, 1UL << PA_SECTION_SHIFT);
|
||||
end = min_t(unsigned long, base->end, (1UL << MAX_PHYSMEM_BITS) - 1);
|
||||
addr = end - size + 1UL;
|
||||
size = ALIGN(size, align);
|
||||
|
||||
res = alloc_resource(GFP_KERNEL);
|
||||
if (!res)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (dev) {
|
||||
if (dev && (flags & GFR_REQUEST_REGION)) {
|
||||
dr = devres_alloc(devm_region_release,
|
||||
sizeof(struct region_devres), GFP_KERNEL);
|
||||
if (!dr) {
|
||||
free_resource(res);
|
||||
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);
|
||||
for (; addr > size && addr >= base->start; addr -= size) {
|
||||
if (__region_intersects(addr, size, 0, IORES_DESC_NONE) !=
|
||||
REGION_DISJOINT)
|
||||
for (addr = gfr_start(base, size, align, flags);
|
||||
gfr_continue(base, addr, size, flags);
|
||||
addr = gfr_next(addr, size, flags)) {
|
||||
if (__region_intersects(base, addr, size, 0, IORES_DESC_NONE) !=
|
||||
REGION_DISJOINT)
|
||||
continue;
|
||||
|
||||
if (__request_region_locked(res, &iomem_resource, addr, size,
|
||||
name, 0))
|
||||
break;
|
||||
if (flags & GFR_REQUEST_REGION) {
|
||||
if (__request_region_locked(res, &iomem_resource, addr,
|
||||
size, name, 0))
|
||||
break;
|
||||
|
||||
if (dev) {
|
||||
dr->parent = &iomem_resource;
|
||||
dr->start = addr;
|
||||
dr->n = size;
|
||||
devres_add(dev, dr);
|
||||
if (dev) {
|
||||
dr->parent = &iomem_resource;
|
||||
dr->start = addr;
|
||||
dr->n = size;
|
||||
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;
|
||||
}
|
||||
write_unlock(&resource_lock);
|
||||
|
||||
free_resource(res);
|
||||
if (dr)
|
||||
if (flags & GFR_REQUEST_REGION) {
|
||||
free_resource(res);
|
||||
devres_free(dr);
|
||||
} else if (dev)
|
||||
devm_release_action(dev, remove_free_mem_region, res);
|
||||
|
||||
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 *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);
|
||||
|
||||
struct resource *request_free_mem_region(struct resource *base,
|
||||
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);
|
||||
|
||||
#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)
|
||||
{
|
||||
|
|
|
@ -983,9 +983,14 @@ config HMM_MIRROR
|
|||
bool
|
||||
depends on MMU
|
||||
|
||||
config GET_FREE_REGION
|
||||
depends on SPARSEMEM
|
||||
bool
|
||||
|
||||
config DEVICE_PRIVATE
|
||||
bool "Unaddressable device memory (GPU memory, ...)"
|
||||
depends on ZONE_DEVICE
|
||||
select GET_FREE_REGION
|
||||
|
||||
help
|
||||
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)/pci.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
|
||||
|
||||
obj-m += test/
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#define NR_CXL_HOST_BRIDGES 2
|
||||
#define NR_CXL_ROOT_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_host_bridge[NR_CXL_HOST_BRIDGES];
|
||||
|
@ -118,7 +118,7 @@ static struct {
|
|||
.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
|
||||
ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
|
||||
.qtg_id = 0,
|
||||
.window_size = SZ_256M,
|
||||
.window_size = SZ_256M * 4UL,
|
||||
},
|
||||
.target = { 0 },
|
||||
},
|
||||
|
@ -133,7 +133,7 @@ static struct {
|
|||
.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
|
||||
ACPI_CEDT_CFMWS_RESTRICT_VOLATILE,
|
||||
.qtg_id = 1,
|
||||
.window_size = SZ_256M * 2,
|
||||
.window_size = SZ_256M * 8UL,
|
||||
},
|
||||
.target = { 0, 1, },
|
||||
},
|
||||
|
@ -148,7 +148,7 @@ static struct {
|
|||
.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
|
||||
ACPI_CEDT_CFMWS_RESTRICT_PMEM,
|
||||
.qtg_id = 2,
|
||||
.window_size = SZ_256M,
|
||||
.window_size = SZ_256M * 4UL,
|
||||
},
|
||||
.target = { 0 },
|
||||
},
|
||||
|
@ -163,7 +163,7 @@ static struct {
|
|||
.restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 |
|
||||
ACPI_CEDT_CFMWS_RESTRICT_PMEM,
|
||||
.qtg_id = 3,
|
||||
.window_size = SZ_256M * 2,
|
||||
.window_size = SZ_256M * 8UL,
|
||||
},
|
||||
.target = { 0, 1, },
|
||||
},
|
||||
|
@ -429,6 +429,50 @@ static int map_targets(struct device *dev, void *data)
|
|||
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)
|
||||
{
|
||||
struct cxl_port *port = cxlhdm->port;
|
||||
|
@ -451,25 +495,39 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm)
|
|||
struct cxl_decoder *cxld;
|
||||
int rc;
|
||||
|
||||
if (target_count)
|
||||
cxld = cxl_switch_decoder_alloc(port, target_count);
|
||||
else
|
||||
cxld = cxl_endpoint_decoder_alloc(port);
|
||||
if (IS_ERR(cxld)) {
|
||||
dev_warn(&port->dev,
|
||||
"Failed to allocate the decoder\n");
|
||||
return PTR_ERR(cxld);
|
||||
if (target_count) {
|
||||
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;
|
||||
} 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,
|
||||
.end = -1,
|
||||
};
|
||||
|
||||
cxld->flags = CXL_DECODER_F_ENABLE;
|
||||
cxld->interleave_ways = min_not_zero(target_count, 1);
|
||||
cxld->interleave_granularity = SZ_4K;
|
||||
cxld->target_type = CXL_DECODER_EXPANDER;
|
||||
cxld->commit = mock_decoder_commit;
|
||||
cxld->reset = mock_decoder_reset;
|
||||
|
||||
if (target_count) {
|
||||
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)
|
||||
#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)
|
||||
{
|
||||
int rc, i;
|
||||
|
@ -619,7 +639,8 @@ static __init int cxl_test_init(void)
|
|||
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)
|
||||
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 *pdev;
|
||||
|
||||
pdev = alloc_memdev(i);
|
||||
pdev = platform_device_alloc("cxl_mem", i);
|
||||
if (!pdev)
|
||||
goto err_mem;
|
||||
pdev->dev.parent = &dport->dev;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <cxlmem.h>
|
||||
|
||||
#define LSA_SIZE SZ_128K
|
||||
#define DEV_SIZE SZ_2G
|
||||
#define EFFECT(x) (1U << x)
|
||||
|
||||
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),
|
||||
.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),
|
||||
.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)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(cxlds->dev);
|
||||
struct cxl_mbox_identify id = {
|
||||
.fw_revision = { "mock fw v1 " },
|
||||
.lsa_size = cpu_to_le32(LSA_SIZE),
|
||||
/* FIXME: Add partition support */
|
||||
.partition_align = cpu_to_le64(0),
|
||||
.partition_align =
|
||||
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))
|
||||
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));
|
||||
|
||||
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)
|
||||
{
|
||||
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:
|
||||
rc = mock_get_lsa(cxlds, cmd);
|
||||
break;
|
||||
case CXL_MBOX_OP_GET_PARTITION_INFO:
|
||||
rc = mock_partition_info(cxlds, cmd);
|
||||
break;
|
||||
case CXL_MBOX_OP_SET_LSA:
|
||||
rc = mock_set_lsa(cxlds, cmd);
|
||||
break;
|
||||
|
@ -282,7 +285,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev)
|
|||
if (IS_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);
|
||||
|
||||
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);
|
||||
|
||||
bool __wrap_cxl_hdm_decode_init(struct cxl_dev_state *cxlds,
|
||||
struct cxl_hdm *cxlhdm)
|
||||
int __wrap_cxl_hdm_decode_init(struct cxl_dev_state *cxlds,
|
||||
struct cxl_hdm *cxlhdm)
|
||||
{
|
||||
int rc = 0, 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);
|
||||
put_cxl_mock_ops(index);
|
||||
|
||||
|
|
Loading…
Reference in New Issue