Commit 0c66a95c authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull CXL (Compute Express Link) updates from Dan Williams:
 "This subsystem is still in the build-out phase as the bulk of the
  update is improvements to enumeration and fleshing out the device
  model. In terms of new features, more mailbox commands have been added
  to the allowed-list in support of persistent memory provisioning
  support targeting v5.15.

  The critical update from an enumeration perspective is support for the
  CXL Fixed Memory Window Structure that indicates to Linux which system
  physical address ranges decode to the CXL Host Bridges in the system.
  This allows the driver to detect which address ranges have been mapped
  by firmware and what address ranges are available for future hotplug.

  So, again, mostly skeleton this round, with more meat targeting v5.15.

  Summary:

   - Add support for the CXL Fixed Memory Window Structure, a recent
     extension of the ACPI CEDT (CXL Early Discovery Table)

   - Add infrastructure for component registers

   - Add HDM (Host-managed device memory) decoder definitions

   - Define a device model for an HDM decoder tree

   - Bridge CXL persistent memory capabilities to an NVDIMM bus /
     device-model

   - Switch to fine grained mapping of CXL MMIO registers to allow
     different drivers / system software to own individual register
     blocks

   - Enable media provisioning commands, and publish the label storage
     area size in sysfs

   - Miscellaneous cleanups and fixes"

* tag 'cxl-for-5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl: (34 commits)
  cxl/pci: Rename CXL REGLOC ID
  cxl/acpi: Use the ACPI CFMWS to create static decoder objects
  cxl/acpi: Add the Host Bridge base address to CXL port objects
  cxl/pmem: Register 'pmem' / cxl_nvdimm devices
  libnvdimm: Drop unused device power management support
  libnvdimm: Export nvdimm shutdown helper, nvdimm_delete()
  cxl/pmem: Add initial infrastructure for pmem support
  cxl/core: Add cxl-bus driver infrastructure
  cxl/pci: Add media provisioning required commands
  cxl/component_regs: Fix offset
  cxl/hdm: Fix decoder count calculation
  cxl/acpi: Introduce cxl_decoder objects
  cxl/acpi: Enumerate host bridge root ports
  cxl/acpi: Add downstream port data to cxl_port instances
  cxl/Kconfig: Default drivers to CONFIG_CXL_BUS
  cxl/acpi: Introduce the root of a cxl_port topology
  cxl/pci: Fixup devm_cxl_iomap_block() to take a 'struct device *'
  cxl/pci: Add HDM decoder capabilities
  cxl/pci: Reserve individual register block regions
  cxl/pci: Map registers based on capabilities
  ...
parents 855ff900 4ad6181e
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
@@ -24,3 +24,106 @@ Description:
		(RO) "Persistent Only Capacity" as bytes. Represents the
		identically named field in the Identify Memory Device Output
		Payload in the CXL-2.0 specification.

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.

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
		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.

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
		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.

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.

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.

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
		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
		memory regions are activated in this decode hierarchy.
+13 −7
Original line number Diff line number Diff line
@@ -22,16 +22,22 @@ This section covers the driver infrastructure for a CXL memory device.
CXL Memory Device
-----------------

.. kernel-doc:: drivers/cxl/mem.c
   :doc: cxl mem
.. kernel-doc:: drivers/cxl/pci.c
   :doc: cxl pci

.. kernel-doc:: drivers/cxl/mem.c
.. kernel-doc:: drivers/cxl/pci.c
   :internal:

CXL Bus
-------
.. kernel-doc:: drivers/cxl/bus.c
   :doc: cxl bus
CXL Core
--------
.. kernel-doc:: drivers/cxl/cxl.h
   :doc: cxl objects

.. kernel-doc:: drivers/cxl/cxl.h
   :internal:

.. kernel-doc:: drivers/cxl/core.c
   :doc: cxl core

External Interfaces
===================
+34 −9
Original line number Diff line number Diff line
@@ -15,21 +15,17 @@ if CXL_BUS

config CXL_MEM
	tristate "CXL.mem: Memory Devices"
	default CXL_BUS
	help
	  The CXL.mem protocol allows a device to act as a provider of
	  "System RAM" and/or "Persistent Memory" that is fully coherent
	  as if the memory was attached to the typical CPU memory
	  controller.

	  Say 'y/m' to enable a driver (named "cxl_mem.ko" when built as
	  a module) that will attach to CXL.mem devices for
	  configuration, provisioning, and health monitoring. This
	  driver is required for dynamic provisioning of CXL.mem
	  attached memory which is a prerequisite for persistent memory
	  support. Typically volatile memory is mapped by platform
	  firmware and included in the platform memory map, but in some
	  cases the OS is responsible for mapping that memory. See
	  Chapter 2.3 Type 3 CXL Device in the CXL 2.0 specification.
	  Say 'y/m' to enable a driver that will attach to CXL.mem devices for
	  configuration and management primarily via the mailbox interface. See
	  Chapter 2.3 Type 3 CXL Device in the CXL 2.0 specification for more
	  details.

	  If unsure say 'm'.

@@ -50,4 +46,33 @@ config CXL_MEM_RAW_COMMANDS
	  potential impact to memory currently in use by the kernel.

	  If developing CXL hardware or the driver say Y, otherwise say N.

config CXL_ACPI
	tristate "CXL ACPI: Platform Support"
	depends on ACPI
	default CXL_BUS
	help
	  Enable support for host managed device memory (HDM) resources
	  published by a platform's ACPI CXL memory layout description.  See
	  Chapter 9.14.1 CXL Early Discovery Table (CEDT) in the CXL 2.0
	  specification, and CXL Fixed Memory Window Structures (CEDT.CFMWS)
	  (https://www.computeexpresslink.org/spec-landing). The CXL core
	  consumes these resource to publish the root of a cxl_port decode
	  hierarchy to map regions that represent System RAM, or Persistent
	  Memory regions to be managed by LIBNVDIMM.

	  If unsure say 'm'.

config CXL_PMEM
	tristate "CXL PMEM: Persistent Memory Support"
	depends on LIBNVDIMM
	default CXL_BUS
	help
	  In addition to typical memory resources a platform may also advertise
	  support for persistent memory attached via CXL. This support is
	  managed via a bridge driver from CXL to the LIBNVDIMM system
	  subsystem. Say 'y/m' to enable support for enumerating and
	  provisioning the persistent memory capacity of CXL memory expanders.

	  If unsure say 'm'.
endif
+8 −4
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_bus.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_BUS) += cxl_core.o
obj-$(CONFIG_CXL_MEM) += cxl_pci.o
obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o

ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE=CXL
cxl_bus-y := bus.o
cxl_mem-y := mem.o
cxl_core-y := core.o
cxl_pci-y := pci.o
cxl_acpi-y := acpi.o
cxl_pmem-y := pmem.o

drivers/cxl/acpi.c

0 → 100644
+434 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/acpi.h>
#include <linux/pci.h>
#include "cxl.h"

static struct acpi_table_header *acpi_cedt;

/* 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 = 0;

	if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE2)
		flags |= CXL_DECODER_F_TYPE2;
	if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE3)
		flags |= CXL_DECODER_F_TYPE3;
	if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE)
		flags |= CXL_DECODER_F_RAM;
	if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM)
		flags |= CXL_DECODER_F_PMEM;
	if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED)
		flags |= CXL_DECODER_F_LOCK;

	return flags;
}

static int cxl_acpi_cfmws_verify(struct device *dev,
				 struct acpi_cedt_cfmws *cfmws)
{
	int expected_len;

	if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
		dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
		return -EINVAL;
	}

	if (!IS_ALIGNED(cfmws->base_hpa, SZ_256M)) {
		dev_err(dev, "CFMWS Base HPA not 256MB aligned\n");
		return -EINVAL;
	}

	if (!IS_ALIGNED(cfmws->window_size, SZ_256M)) {
		dev_err(dev, "CFMWS Window Size not 256MB aligned\n");
		return -EINVAL;
	}

	expected_len = struct_size((cfmws), interleave_targets,
				   CFMWS_INTERLEAVE_WAYS(cfmws));

	if (cfmws->header.length < expected_len) {
		dev_err(dev, "CFMWS length %d less than expected %d\n",
			cfmws->header.length, expected_len);
		return -EINVAL;
	}

	if (cfmws->header.length > expected_len)
		dev_dbg(dev, "CFMWS length %d greater than expected %d\n",
			cfmws->header.length, expected_len);

	return 0;
}

static void cxl_add_cfmws_decoders(struct device *dev,
				   struct cxl_port *root_port)
{
	struct acpi_cedt_cfmws *cfmws;
	struct cxl_decoder *cxld;
	acpi_size len, cur = 0;
	void *cedt_subtable;
	unsigned long flags;
	int rc;

	len = acpi_cedt->length - sizeof(*acpi_cedt);
	cedt_subtable = acpi_cedt + 1;

	while (cur < len) {
		struct acpi_cedt_header *c = cedt_subtable + cur;

		if (c->type != ACPI_CEDT_TYPE_CFMWS) {
			cur += c->length;
			continue;
		}

		cfmws = cedt_subtable + cur;

		if (cfmws->header.length < sizeof(*cfmws)) {
			dev_warn_once(dev,
				      "CFMWS entry skipped:invalid length:%u\n",
				      cfmws->header.length);
			cur += c->length;
			continue;
		}

		rc = cxl_acpi_cfmws_verify(dev, cfmws);
		if (rc) {
			dev_err(dev, "CFMWS range %#llx-%#llx not registered\n",
				cfmws->base_hpa, cfmws->base_hpa +
				cfmws->window_size - 1);
			cur += c->length;
			continue;
		}

		flags = cfmws_to_decoder_flags(cfmws->restrictions);
		cxld = devm_cxl_add_decoder(dev, root_port,
					    CFMWS_INTERLEAVE_WAYS(cfmws),
					    cfmws->base_hpa, cfmws->window_size,
					    CFMWS_INTERLEAVE_WAYS(cfmws),
					    CFMWS_INTERLEAVE_GRANULARITY(cfmws),
					    CXL_DECODER_EXPANDER,
					    flags);

		if (IS_ERR(cxld)) {
			dev_err(dev, "Failed to add decoder for %#llx-%#llx\n",
				cfmws->base_hpa, cfmws->base_hpa +
				cfmws->window_size - 1);
		} else {
			dev_dbg(dev, "add: %s range %#llx-%#llx\n",
				dev_name(&cxld->dev), cfmws->base_hpa,
				 cfmws->base_hpa + cfmws->window_size - 1);
		}
		cur += c->length;
	}
}

static struct acpi_cedt_chbs *cxl_acpi_match_chbs(struct device *dev, u32 uid)
{
	struct acpi_cedt_chbs *chbs, *chbs_match = NULL;
	acpi_size len, cur = 0;
	void *cedt_subtable;

	len = acpi_cedt->length - sizeof(*acpi_cedt);
	cedt_subtable = acpi_cedt + 1;

	while (cur < len) {
		struct acpi_cedt_header *c = cedt_subtable + cur;

		if (c->type != ACPI_CEDT_TYPE_CHBS) {
			cur += c->length;
			continue;
		}

		chbs = cedt_subtable + cur;

		if (chbs->header.length < sizeof(*chbs)) {
			dev_warn_once(dev,
				      "CHBS entry skipped: invalid length:%u\n",
				      chbs->header.length);
			cur += c->length;
			continue;
		}

		if (chbs->uid != uid) {
			cur += c->length;
			continue;
		}

		if (chbs_match) {
			dev_warn_once(dev,
				      "CHBS entry skipped: duplicate UID:%u\n",
				      uid);
			cur += c->length;
			continue;
		}

		chbs_match = chbs;
		cur += c->length;
	}

	return chbs_match ? chbs_match : ERR_PTR(-ENODEV);
}

static resource_size_t get_chbcr(struct acpi_cedt_chbs *chbs)
{
	return IS_ERR(chbs) ? CXL_RESOURCE_NONE : chbs->base;
}

struct cxl_walk_context {
	struct device *dev;
	struct pci_bus *root;
	struct cxl_port *port;
	int error;
	int count;
};

static int match_add_root_ports(struct pci_dev *pdev, void *data)
{
	struct cxl_walk_context *ctx = data;
	struct pci_bus *root_bus = ctx->root;
	struct cxl_port *port = ctx->port;
	int type = pci_pcie_type(pdev);
	struct device *dev = ctx->dev;
	u32 lnkcap, port_num;
	int rc;

	if (pdev->bus != root_bus)
		return 0;
	if (!pci_is_pcie(pdev))
		return 0;
	if (type != PCI_EXP_TYPE_ROOT_PORT)
		return 0;
	if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
				  &lnkcap) != PCIBIOS_SUCCESSFUL)
		return 0;

	/* TODO walk DVSEC to find component register base */
	port_num = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
	rc = cxl_add_dport(port, &pdev->dev, port_num, CXL_RESOURCE_NONE);
	if (rc) {
		ctx->error = rc;
		return rc;
	}
	ctx->count++;

	dev_dbg(dev, "add dport%d: %s\n", port_num, dev_name(&pdev->dev));

	return 0;
}

static struct cxl_dport *find_dport_by_dev(struct cxl_port *port, struct device *dev)
{
	struct cxl_dport *dport;

	device_lock(&port->dev);
	list_for_each_entry(dport, &port->dports, list)
		if (dport->dport == dev) {
			device_unlock(&port->dev);
			return dport;
		}

	device_unlock(&port->dev);
	return NULL;
}

static struct acpi_device *to_cxl_host_bridge(struct device *dev)
{
	struct acpi_device *adev = to_acpi_device(dev);

	if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0)
		return adev;
	return NULL;
}

/*
 * A host bridge is a dport to a CFMWS decode and it is a uport to the
 * dport (PCIe Root Ports) in the host bridge.
 */
static int add_host_bridge_uport(struct device *match, void *arg)
{
	struct acpi_device *bridge = to_cxl_host_bridge(match);
	struct cxl_port *root_port = arg;
	struct device *host = root_port->dev.parent;
	struct acpi_pci_root *pci_root;
	struct cxl_walk_context ctx;
	struct cxl_decoder *cxld;
	struct cxl_dport *dport;
	struct cxl_port *port;

	if (!bridge)
		return 0;

	pci_root = acpi_pci_find_root(bridge->handle);
	if (!pci_root)
		return -ENXIO;

	dport = find_dport_by_dev(root_port, match);
	if (!dport) {
		dev_dbg(host, "host bridge expected and not found\n");
		return -ENODEV;
	}

	port = devm_cxl_add_port(host, match, dport->component_reg_phys,
				 root_port);
	if (IS_ERR(port))
		return PTR_ERR(port);
	dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev));

	ctx = (struct cxl_walk_context){
		.dev = host,
		.root = pci_root->bus,
		.port = port,
	};
	pci_walk_bus(pci_root->bus, match_add_root_ports, &ctx);

	if (ctx.count == 0)
		return -ENODEV;
	if (ctx.error)
		return ctx.error;

	/* TODO: Scan CHBCR for HDM Decoder resources */

	/*
	 * In the single-port host-bridge case there are no HDM decoders
	 * in the CHBCR and a 1:1 passthrough decode is implied.
	 */
	if (ctx.count == 1) {
		cxld = devm_cxl_add_passthrough_decoder(host, port);
		if (IS_ERR(cxld))
			return PTR_ERR(cxld);

		dev_dbg(host, "add: %s\n", dev_name(&cxld->dev));
	}

	return 0;
}

static int add_host_bridge_dport(struct device *match, void *arg)
{
	int rc;
	acpi_status status;
	unsigned long long uid;
	struct acpi_cedt_chbs *chbs;
	struct cxl_port *root_port = arg;
	struct device *host = root_port->dev.parent;
	struct acpi_device *bridge = to_cxl_host_bridge(match);

	if (!bridge)
		return 0;

	status = acpi_evaluate_integer(bridge->handle, METHOD_NAME__UID, NULL,
				       &uid);
	if (status != AE_OK) {
		dev_err(host, "unable to retrieve _UID of %s\n",
			dev_name(match));
		return -ENODEV;
	}

	chbs = cxl_acpi_match_chbs(host, uid);
	if (IS_ERR(chbs))
		dev_dbg(host, "No CHBS found for Host Bridge: %s\n",
			dev_name(match));

	rc = cxl_add_dport(root_port, match, uid, get_chbcr(chbs));
	if (rc) {
		dev_err(host, "failed to add downstream port: %s\n",
			dev_name(match));
		return rc;
	}
	dev_dbg(host, "add dport%llu: %s\n", uid, dev_name(match));
	return 0;
}

static int add_root_nvdimm_bridge(struct device *match, void *data)
{
	struct cxl_decoder *cxld;
	struct cxl_port *root_port = data;
	struct cxl_nvdimm_bridge *cxl_nvb;
	struct device *host = root_port->dev.parent;

	if (!is_root_decoder(match))
		return 0;

	cxld = to_cxl_decoder(match);
	if (!(cxld->flags & CXL_DECODER_F_PMEM))
		return 0;

	cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port);
	if (IS_ERR(cxl_nvb)) {
		dev_dbg(host, "failed to register pmem\n");
		return PTR_ERR(cxl_nvb);
	}
	dev_dbg(host, "%s: add: %s\n", dev_name(&root_port->dev),
		dev_name(&cxl_nvb->dev));
	return 1;
}

static int cxl_acpi_probe(struct platform_device *pdev)
{
	int rc;
	acpi_status status;
	struct cxl_port *root_port;
	struct device *host = &pdev->dev;
	struct acpi_device *adev = ACPI_COMPANION(host);

	root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
	if (IS_ERR(root_port))
		return PTR_ERR(root_port);
	dev_dbg(host, "add: %s\n", dev_name(&root_port->dev));

	status = acpi_get_table(ACPI_SIG_CEDT, 0, &acpi_cedt);
	if (ACPI_FAILURE(status))
		return -ENXIO;

	rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
			      add_host_bridge_dport);
	if (rc)
		goto out;

	cxl_add_cfmws_decoders(host, root_port);

	/*
	 * Root level scanned with host-bridge as dports, now scan host-bridges
	 * for their role as CXL uports to their CXL-capable PCIe Root Ports.
	 */
	rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
			      add_host_bridge_uport);
	if (rc)
		goto out;

	if (IS_ENABLED(CONFIG_CXL_PMEM))
		rc = device_for_each_child(&root_port->dev, root_port,
					   add_root_nvdimm_bridge);

out:
	acpi_put_table(acpi_cedt);
	if (rc < 0)
		return rc;
	return 0;
}

static const struct acpi_device_id cxl_acpi_ids[] = {
	{ "ACPI0017", 0 },
	{ "", 0 },
};
MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);

static struct platform_driver cxl_acpi_driver = {
	.probe = cxl_acpi_probe,
	.driver = {
		.name = KBUILD_MODNAME,
		.acpi_match_table = cxl_acpi_ids,
	},
};

module_platform_driver(cxl_acpi_driver);
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS(CXL);
Loading