From: Alastair D'Silva <redacted>
This series adds support for OpenCAPI SCM devices, exposing
them as nvdimms so that we can make use of the existing
infrastructure.
V2:
- "powerpc: Map & release OpenCAPI LPC memory"
- Fix #if -> #ifdef
- use pci_dev_id to get the bdfn
- use __be64 to hold be data
- indent check_hotplug_memory_addressable correctly
- Remove export of check_hotplug_memory_addressable
- "ocxl: Conditionally bind SCM devices to the generic OCXL driver"
- Improve patch description and remove redundant default
- "nvdimm: Add driver for OpenCAPI Storage Class Memory"
- Mark a few funcs as static as identified by the 0day bot
- Add OCXL dependancies to OCXL_SCM
- Use memcpy_mcsafe in scm_ndctl_config_read
- Rename scm_foo_offset_0x00 to scm_foo_header_parse & add docs
- Name DIMM attribs "ocxl" rather than "scm"
- Split out into base + many feature patches
- "powerpc: Enable OpenCAPI Storage Class Memory driver on bare metal"
- Build DEV_DAX & friends as modules
- "ocxl: Conditionally bind SCM devices to the generic OCXL driver"
- Patch dropped (easy enough to maintain this out of tree for development)
- "ocxl: Tally up the LPC memory on a link & allow it to be mapped"
- Add a warning if an unmatched lpc_release is called
- "ocxl: Add functions to map/unmap LPC memory"
- Use EXPORT_SYMBOL_GPL
Alastair D'Silva (27):
memory_hotplug: Add a bounds check to __add_pages
nvdimm: remove prototypes for nonexistent functions
powerpc: Add OPAL calls for LPC memory alloc/release
mm/memory_hotplug: Allow check_hotplug_memory_addressable to be called
from drivers
powerpc: Map & release OpenCAPI LPC memory
ocxl: Tally up the LPC memory on a link & allow it to be mapped
ocxl: Add functions to map/unmap LPC memory
ocxl: Save the device serial number in ocxl_fn
ocxl: Free detached contexts in ocxl_context_detach_all()
nvdimm: Add driver for OpenCAPI Storage Class Memory
nvdimm/ocxl: Add register addresses & status values to header
nvdimm/ocxl: Read the capability registers & wait for device ready
nvdimm/ocxl: Add support for Admin commands
nvdimm/ocxl: Add support for near storage commands
nvdimm/ocxl: Register a character device for userspace to interact
with
nvdimm/ocxl: Implement the Read Error Log command
nvdimm/ocxl: Add controller dump IOCTLs
nvdimm/ocxl: Add an IOCTL to report controller statistics
nvdimm/ocxl: Forward events to userspace
nvdimm/ocxl: Add an IOCTL to request controller health & perf data
nvdimm/ocxl: Support firmware update via sysfs
nvdimm/ocxl: Implement the heartbeat command
nvdimm/ocxl: Add debug IOCTLs
nvdimm/ocxl: Implement Overwrite
nvdimm/ocxl: Expose SMART data via ndctl
powerpc: Enable OpenCAPI Storage Class Memory driver on bare metal
MAINTAINERS: Add myself & nvdimm/ocxl to ocxl
MAINTAINERS | 3 +
arch/powerpc/configs/powernv_defconfig | 4 +
arch/powerpc/include/asm/opal-api.h | 2 +
arch/powerpc/include/asm/opal.h | 3 +
arch/powerpc/include/asm/pnv-ocxl.h | 2 +
arch/powerpc/platforms/powernv/ocxl.c | 42 +
arch/powerpc/platforms/powernv/opal-call.c | 2 +
drivers/misc/ocxl/config.c | 50 +
drivers/misc/ocxl/context.c | 6 +-
drivers/misc/ocxl/core.c | 60 +
drivers/misc/ocxl/link.c | 60 +
drivers/misc/ocxl/ocxl_internal.h | 36 +
drivers/nvdimm/Kconfig | 2 +
drivers/nvdimm/Makefile | 2 +-
drivers/nvdimm/nd-core.h | 4 -
drivers/nvdimm/ocxl/Kconfig | 21 +
drivers/nvdimm/ocxl/Makefile | 7 +
drivers/nvdimm/ocxl/scm.c | 2220 ++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.c | 238 +++
drivers/nvdimm/ocxl/scm_internal.h | 284 +++
drivers/nvdimm/ocxl/scm_sysfs.c | 163 ++
include/linux/memory_hotplug.h | 5 +
include/misc/ocxl.h | 19 +
include/uapi/nvdimm/ocxl-scm.h | 127 ++
mm/memory_hotplug.c | 21 +
25 files changed, 3377 insertions(+), 6 deletions(-)
create mode 100644 drivers/nvdimm/ocxl/Kconfig
create mode 100644 drivers/nvdimm/ocxl/Makefile
create mode 100644 drivers/nvdimm/ocxl/scm.c
create mode 100644 drivers/nvdimm/ocxl/scm_internal.c
create mode 100644 drivers/nvdimm/ocxl/scm_internal.h
create mode 100644 drivers/nvdimm/ocxl/scm_sysfs.c
create mode 100644 include/uapi/nvdimm/ocxl-scm.h
--
2.23.0
From: Dan Williams <hidden> Date: 2019-12-04 00:10:37
On Mon, Dec 2, 2019 at 7:48 PM Alastair D'Silva [off-list ref] wrote:
From: Alastair D'Silva <redacted>
These functions don't exist, so remove the prototypes for them.
Signed-off-by: Alastair D'Silva <redacted>
Reviewed-by: Frederic Barrat <redacted>
---
This was already merged as commit:
cda93d6965a1 libnvdimm: Remove prototypes for nonexistent functions
You should have received a notification from the patchwork bot that it
was already accepted.
What baseline did you use for this submission?
From: Dan Williams <hidden> Date: 2020-01-23 21:49:49
[ add Aneesh ]
On Tue, Dec 3, 2019 at 4:10 PM Dan Williams [off-list ref] wrote:
On Mon, Dec 2, 2019 at 7:48 PM Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
These functions don't exist, so remove the prototypes for them.
Signed-off-by: Alastair D'Silva <redacted>
Reviewed-by: Frederic Barrat <redacted>
---
This was already merged as commit:
cda93d6965a1 libnvdimm: Remove prototypes for nonexistent functions
You should have received a notification from the patchwork bot that it
was already accepted.
What baseline did you use for this submission?
I never got an answer to this, and I have not seen any updates. Can I
ask you to get an initial review from Aneesh who has been doing good
work in the nvdimm core, and then we can look to get this in the
pipeline for the v5.7 kernel?
From: Alastair D'Silva <redacted>
On PowerPC, the address ranges allocated to OpenCAPI LPC memory
are allocated from firmware. These address ranges may be higher
than what older kernels permit, as we increased the maximum
permissable address in commit 4ffe713b7587
("powerpc/mm: Increase the max addressable memory to 2PB"). It is
possible that the addressable range may change again in the
future.
In this scenario, we end up with a bogus section returned from
__section_nr (see the discussion on the thread "mm: Trigger bug on
if a section is not found in __section_nr").
Adding a check here means that we fail early and have an
opportunity to handle the error gracefully, rather than rumbling
on and potentially accessing an incorrect section.
Further discussion is also on the thread ("powerpc: Perform a bounds
check in arch_add_memory")
http://lkml.kernel.org/r/20190827052047.31547-1-alastair@au1.ibm.com
Signed-off-by: Alastair D'Silva <redacted>
Reviewed-by: David Hildenbrand <redacted>
Acked-by: Michal Hocko <mhocko@suse.com>
---
mm/memory_hotplug.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
@@ -278,6 +278,23 @@ static int check_pfn_span(unsigned long pfn, unsigned long nr_pages,return0;}+staticintcheck_hotplug_memory_addressable(unsignedlongpfn,+unsignedlongnr_pages)+{+constu64max_addr=PFN_PHYS(pfn+nr_pages)-1;++if(max_addr>>MAX_PHYSMEM_BITS){+constu64max_allowed=(1ull<<(MAX_PHYSMEM_BITS+1))-1;++WARN(1,+"Hotplugged memory exceeds maximum addressable address, range=%#llx-%#llx, maximum=%#llx\n",+PFN_PHYS(pfn),max_addr,max_allowed);+return-E2BIG;+}++return0;+}+/**Reasonablygenericfunctionforaddingmemory.Itis*expectedthatarchsthatsupportmemoryhotplugwill
@@ -291,6 +308,10 @@ int __ref __add_pages(int nid, unsigned long pfn, unsigned long nr_pages,unsignedlongnr,start_sec,end_sec;structvmem_altmap*altmap=restrictions->altmap;+err=check_hotplug_memory_addressable(pfn,nr_pages);+if(err)+returnerr;+if(altmap){/**Validatealtmapiswithinboundsofthetotalrequest
From: Alastair D'Silva <redacted>
This patch reads timeouts & firmware version from the controller, and
uses those timeouts to wait for the controller to report that it is ready
before handing the memory over to libnvdimm.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/Makefile | 2 +-
drivers/nvdimm/ocxl/scm.c | 84 ++++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.c | 19 +++++++
drivers/nvdimm/ocxl/scm_internal.h | 24 +++++++++
4 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvdimm/ocxl/scm_internal.c
@@ -266,6 +267,30 @@ static int scm_register_lpc_mem(struct scm_data *scm_data)return0;}+/**+*scm_is_usable()-Isacontrollerusable?+*@scm_data:apointertotheSCMdevicedata+*Return:trueifthecontrollerisusable+*/+staticboolscm_is_usable(conststructscm_data*scm_data)+{+u64chi=0;+intrc=scm_chi(scm_data,&chi);++if(!(chi&GLOBAL_MMIO_CHI_CRDY)){+dev_err(&scm_data->dev,"SCM controller is not ready.\n");+returnfalse;+}++if(!(chi&GLOBAL_MMIO_CHI_MA)){+dev_err(&scm_data->dev,+"SCM controller does not have memory available.\n");+returnfalse;+}++returntrue;+}+/***allocate_scm_minor()-AllocateaminornumbertouseforanSCMdevice*@scm_data:TheSCMdevicetoassociatetheminorwith
@@ -380,6 +405,48 @@ static void scm_remove(struct pci_dev *pdev)}}+/**+*read_device_metadata()-RetrieveconfiginformationfromtheAFUandsaveitforfutureuse+*@scm_data:theSCMmetadata+*Return:0onsuccess,negativeonfailure+*/+staticintread_device_metadata(structscm_data*scm_data)+{+u64val;+intrc;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,GLOBAL_MMIO_CCAP0,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++scm_data->scm_revision=val&0xFFFF;+scm_data->read_latency=(val>>32)&0xFF;+scm_data->readiness_timeout=(val>>48)&0xff;+scm_data->memory_available_timeout=val>>52;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,GLOBAL_MMIO_CCAP1,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++scm_data->max_controller_dump_size=val&0xFFFFFFFF;++// Extract firmware version text+rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,GLOBAL_MMIO_FWVER,+OCXL_HOST_ENDIAN,(u64*)scm_data->fw_version);+if(rc)+returnrc;++scm_data->fw_version[8]='\0';++dev_info(&scm_data->dev,+"Firmware version '%s' SCM revision %d:%d\n",scm_data->fw_version,+scm_data->scm_revision>>4,scm_data->scm_revision&0x0F);++return0;+}+/***scm_probe_function_0-Setupfunction0foranOpenCAPIStorageClassMemorydevice*Thisisimportantasitenablestemplateshigherthan0acrossallotherfunctions,
@@ -420,6 +487,8 @@ static int scm_probe_function_0(struct pci_dev *pdev)staticintscm_probe(structpci_dev*pdev,conststructpci_device_id*ent){structscm_data*scm_data=NULL;+intelapsed;+u16timeout;if(PCI_FUNC(pdev->devfn)==0)returnscm_probe_function_0(pdev);
@@ -469,6 +538,21 @@ static int scm_probe(struct pci_dev *pdev, const struct pci_device_id *ent)gotoerr;}+if(read_device_metadata(scm_data)){+dev_err(&pdev->dev,"Could not read SCM device metadata\n");+gotoerr;+}++elapsed=0;+timeout=scm_data->readiness_timeout+scm_data->memory_available_timeout;+while(!scm_is_usable(scm_data)){+if(elapsed++>timeout){+dev_warn(&scm_data->dev,"SCM ready timeout.\n");+gotoerr;+}++msleep(1000);+}if(scm_register_lpc_mem(scm_data)){dev_err(&pdev->dev,"Could not register OCXL SCM memory with libnvdimm\n");gotoerr;
@@ -97,4 +97,28 @@ struct scm_data {void*metadata_addr;structresourcescm_res;structnd_region*nd_region;+charfw_version[8+1];++u32max_controller_dump_size;+u16scm_revision;// major/minor+u8readiness_timeout;/* The worst case time (in seconds) that the host shall+*waitforthecontrollertobecomeoperationalfollowingareset(CHI.CRDY).+*/+u8memory_available_timeout;/* The worst case time (in seconds) that the host shall+*waitformemorytobecomeavailablefollowingareset(CHI.MA).+*/++u16read_latency;/* The nominal measure of latency (in nanoseconds)+*associatedwithanunassistedreadofamemoryblock.+*Thisrepresentsthecapabilityoftherawmediatechnologywithoutassistance+*/};++/**+*scm_chi()-GetthevalueoftheCHIregister+*@scm_data:TheSCMmetadata+*@chi:returnstheCHIvalue+*+*Returns0onsuccess,negativeonerror+*/+intscm_chi(conststructscm_data*scm_data,u64*chi);
From: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Date: 2020-02-03 13:23:58
On Tue, 3 Dec 2019 14:46:40 +1100
Alastair D'Silva [off-list ref] wrote:
quoted hunk
From: Alastair D'Silva <redacted>
This patch reads timeouts & firmware version from the controller, and
uses those timeouts to wait for the controller to report that it is ready
before handing the memory over to libnvdimm.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/Makefile | 2 +-
drivers/nvdimm/ocxl/scm.c | 84 ++++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.c | 19 +++++++
drivers/nvdimm/ocxl/scm_internal.h | 24 +++++++++
4 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvdimm/ocxl/scm_internal.c
@@ -266,6 +267,30 @@ static int scm_register_lpc_mem(struct scm_data *scm_data)return0;}+/**+*scm_is_usable()-Isacontrollerusable?+*@scm_data:apointertotheSCMdevicedata+*Return:trueifthecontrollerisusable+*/+staticboolscm_is_usable(conststructscm_data*scm_data)+{+u64chi=0;+intrc=scm_chi(scm_data,&chi);++if(!(chi&GLOBAL_MMIO_CHI_CRDY)){+dev_err(&scm_data->dev,"SCM controller is not ready.\n");+returnfalse;+}++if(!(chi&GLOBAL_MMIO_CHI_MA)){+dev_err(&scm_data->dev,+"SCM controller does not have memory available.\n");+returnfalse;+}++returntrue;+}+/***allocate_scm_minor()-AllocateaminornumbertouseforanSCMdevice*@scm_data:TheSCMdevicetoassociatetheminorwith
This overlaps with the masked region for readiness_timeout. I'll guess the maks
on that should be 0xF.
quoted hunk
+
+ rc = ocxl_global_mmio_read64(scm_data->ocxl_afu, GLOBAL_MMIO_CCAP1,
+ OCXL_LITTLE_ENDIAN, &val);
+ if (rc)
+ return rc;
+
+ scm_data->max_controller_dump_size = val & 0xFFFFFFFF;
+
+ // Extract firmware version text
+ rc = ocxl_global_mmio_read64(scm_data->ocxl_afu, GLOBAL_MMIO_FWVER,
+ OCXL_HOST_ENDIAN, (u64 *)scm_data->fw_version);
+ if (rc)
+ return rc;
+
+ scm_data->fw_version[8] = '\0';
+
+ dev_info(&scm_data->dev,
+ "Firmware version '%s' SCM revision %d:%d\n", scm_data->fw_version,
+ scm_data->scm_revision >> 4, scm_data->scm_revision & 0x0F);
+
+ return 0;
+}
+
/**
* scm_probe_function_0 - Set up function 0 for an OpenCAPI Storage Class Memory device
* This is important as it enables templates higher than 0 across all other functions,
@@ -420,6 +487,8 @@ static int scm_probe_function_0(struct pci_dev *pdev) static int scm_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct scm_data *scm_data = NULL;+ int elapsed;+ u16 timeout; if (PCI_FUNC(pdev->devfn) == 0) return scm_probe_function_0(pdev);
@@ -469,6 +538,21 @@ static int scm_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto err; }+ if (read_device_metadata(scm_data)) {+ dev_err(&pdev->dev, "Could not read SCM device metadata\n");+ goto err;+ }++ elapsed = 0;+ timeout = scm_data->readiness_timeout + scm_data->memory_available_timeout;+ while (!scm_is_usable(scm_data)) {+ if (elapsed++ > timeout) {+ dev_warn(&scm_data->dev, "SCM ready timeout.\n");+ goto err;+ }++ msleep(1000);+ } if (scm_register_lpc_mem(scm_data)) { dev_err(&pdev->dev, "Could not register OCXL SCM memory with libnvdimm\n"); goto err;
@@ -97,4 +97,28 @@ struct scm_data {void*metadata_addr;structresourcescm_res;structnd_region*nd_region;+charfw_version[8+1];++u32max_controller_dump_size;+u16scm_revision;// major/minor+u8readiness_timeout;/* The worst case time (in seconds) that the host shall+*waitforthecontrollertobecomeoperationalfollowingareset(CHI.CRDY).+*/+u8memory_available_timeout;/* The worst case time (in seconds) that the host shall+*waitformemorytobecomeavailablefollowingareset(CHI.MA).+*/++u16read_latency;/* The nominal measure of latency (in nanoseconds)+*associatedwithanunassistedreadofamemoryblock.+*Thisrepresentsthecapabilityoftherawmediatechnologywithoutassistance+*/};++/**+*scm_chi()-GetthevalueoftheCHIregister+*@scm_data:TheSCMmetadata+*@chi:returnstheCHIvalue+*+*Returns0onsuccess,negativeonerror+*/+intscm_chi(conststructscm_data*scm_data,u64*chi);
On Mon, 2020-02-03 at 13:23 +0000, Jonathan Cameron wrote:
On Tue, 3 Dec 2019 14:46:40 +1100
Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
This patch reads timeouts & firmware version from the controller,
and
uses those timeouts to wait for the controller to report that it is
ready
before handing the memory over to libnvdimm.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/Makefile | 2 +-
drivers/nvdimm/ocxl/scm.c | 84
++++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.c | 19 +++++++
drivers/nvdimm/ocxl/scm_internal.h | 24 +++++++++
4 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvdimm/ocxl/scm_internal.c
diff --git a/drivers/nvdimm/ocxl/Makefile
b/drivers/nvdimm/ocxl/Makefile
index 74a1bd98848e..9b6e31f0eb3e 100644
@@ -266,6 +267,30 @@ static int scm_register_lpc_mem(struct
scm_data *scm_data)
return 0;
}
+/**
+ * scm_is_usable() - Is a controller usable?
+ * @scm_data: a pointer to the SCM device data
+ * Return: true if the controller is usable
+ */
+static bool scm_is_usable(const struct scm_data *scm_data)
+{
+ u64 chi = 0;
+ int rc = scm_chi(scm_data, &chi);
+
+ if (!(chi & GLOBAL_MMIO_CHI_CRDY)) {
+ dev_err(&scm_data->dev, "SCM controller is not
ready.\n");
+ return false;
+ }
+
+ if (!(chi & GLOBAL_MMIO_CHI_MA)) {
+ dev_err(&scm_data->dev,
+ "SCM controller does not have memory
available.\n");
+ return false;
+ }
+
+ return true;
+}
+
/**
* allocate_scm_minor() - Allocate a minor number to use for an
SCM device
* @scm_data: The SCM device to associate the minor with
@@ -380,6 +405,48 @@ static void scm_remove(struct pci_dev *pdev) } }+/**+ * read_device_metadata() - Retrieve config information from the
AFU and save it for future use
+ * @scm_data: the SCM metadata
+ * Return: 0 on success, negative on failure
+ */
+static int read_device_metadata(struct scm_data *scm_data)
+{
+ u64 val;
+ int rc;
+
+ rc = ocxl_global_mmio_read64(scm_data->ocxl_afu,
GLOBAL_MMIO_CCAP0,
+ OCXL_LITTLE_ENDIAN, &val);
+ if (rc)
+ return rc;
+
+ scm_data->scm_revision = val & 0xFFFF;
+ scm_data->read_latency = (val >> 32) & 0xFF;
+ scm_data->readiness_timeout = (val >> 48) & 0xff;
+ scm_data->memory_available_timeout = val >> 52;
This overlaps with the masked region for readiness_timeout. I'll
guess the maks
on that should be 0xF.
Good catch, you're correct.
quoted
+
+ rc = ocxl_global_mmio_read64(scm_data->ocxl_afu,
GLOBAL_MMIO_CCAP1,
+ OCXL_LITTLE_ENDIAN, &val);
+ if (rc)
+ return rc;
+
+ scm_data->max_controller_dump_size = val & 0xFFFFFFFF;
+
+ // Extract firmware version text
+ rc = ocxl_global_mmio_read64(scm_data->ocxl_afu,
GLOBAL_MMIO_FWVER,
+ OCXL_HOST_ENDIAN, (u64 *)scm_data-
quoted
fw_version);
+ if (rc)
+ return rc;
+
+ scm_data->fw_version[8] = '\0';
+
+ dev_info(&scm_data->dev,
+ "Firmware version '%s' SCM revision %d:%d\n",
scm_data->fw_version,
+ scm_data->scm_revision >> 4, scm_data->scm_revision &
0x0F);
+
+ return 0;
+}
+
/**
* scm_probe_function_0 - Set up function 0 for an OpenCAPI
Storage Class Memory device
* This is important as it enables templates higher than 0 across
all other functions,
@@ -420,6 +487,8 @@ static int scm_probe_function_0(struct pci_dev
*pdev)
static int scm_probe(struct pci_dev *pdev, const struct
pci_device_id *ent)
{
struct scm_data *scm_data = NULL;
+ int elapsed;
+ u16 timeout;
if (PCI_FUNC(pdev->devfn) == 0)
return scm_probe_function_0(pdev);
@@ -469,6 +538,21 @@ static int scm_probe(struct pci_dev *pdev,
@@ -97,4 +97,28 @@ struct scm_data {void*metadata_addr;structresourcescm_res;structnd_region*nd_region;+charfw_version[8+1];++u32max_controller_dump_size;+u16scm_revision;// major/minor+u8readiness_timeout;/* The worst case time (in seconds) that
the host shall
+ * wait for the controller to become
operational following a reset (CHI.CRDY).
+ */
+ u8 memory_available_timeout; /* The worst case time (in
seconds) that the host shall
+ * wait for memory to become
available following a reset (CHI.MA).
+ */
+
+ u16 read_latency; /* The nominal measure of latency (in
nanoseconds)
+ * associated with an unassisted read of a
memory block.
+ * This represents the capability of the raw
media technology without assistance
+ */
};
+
+/**
+ * scm_chi() - Get the value of the CHI register
+ * @scm_data: The SCM metadata
+ * @chi: returns the CHI value
+ *
+ * Returns 0 on success, negative on error
+ */
+int scm_chi(const struct scm_data *scm_data, u64 *chi);
--
Alastair D'Silva
Open Source Developer
Linux Technology Centre, IBM Australia
mob: 0423 762 819
From: Alastair D'Silva <redacted>
This patch requests the metadata required to issue admin commands, as well
as some helper functions to construct and check the completion of the
commands.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 67 +++++++++++++
drivers/nvdimm/ocxl/scm_internal.c | 152 +++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 62 ++++++++++++
3 files changed, 281 insertions(+)
@@ -17,3 +17,155 @@ int scm_chi(const struct scm_data *scm_data, u64 *chi)return0;}++staticintscm_command_request(conststructscm_data*scm_data,+structcommand_metadata*cmd,u8op_code)+{+u64val=op_code;+intrc;+u8i;++cmd->op_code=op_code;+cmd->id++;++val|=((u64)cmd->id)<<16;++rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,cmd->request_offset,+OCXL_LITTLE_ENDIAN,val);+if(rc)+returnrc;++for(i=0x08;i<=0x38;i+=0x08){+rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,+cmd->request_offset+i,+OCXL_LITTLE_ENDIAN,0);+if(rc)+returnrc;+}++return0;+}++intscm_admin_command_request(structscm_data*scm_data,u8op_code)+{+u64val;+intrc=ocxl_global_mmio_read64(scm_data->ocxl_afu,GLOBAL_MMIO_CHI,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++returnscm_command_request(scm_data,&scm_data->admin_command,op_code);+}++staticintscm_command_response(conststructscm_data*scm_data,+conststructcommand_metadata*cmd)+{+u64val;+u16id;+u8status;+intrc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+cmd->response_offset,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++status=val&0xff;+id=(val>>16)&0xffff;++if(id!=cmd->id){+dev_warn(&scm_data->dev,+"Expected response for command %d, but received response for command %d instead.\n",+cmd->id,id);+}++returnstatus;+}++intscm_admin_response(conststructscm_data*scm_data)+{+returnscm_command_response(scm_data,&scm_data->admin_command);+}+++intscm_admin_command_execute(conststructscm_data*scm_data)+{+returnocxl_global_mmio_set64(scm_data->ocxl_afu,GLOBAL_MMIO_HCI,+OCXL_LITTLE_ENDIAN,GLOBAL_MMIO_HCI_ACRW);+}++staticboolscm_admin_command_complete(conststructscm_data*scm_data)+{+u64val=0;++intrc=scm_chi(scm_data,&val);++WARN_ON(rc);++return(val&GLOBAL_MMIO_CHI_ACRA)!=0;+}++intscm_admin_command_complete_timeout(conststructscm_data*scm_data,+intcommand)+{+u32timeout=scm_data->timeouts[command];+// 32 is the next power of 2 greater than the 20ms minimum for msleep+#define TIMEOUT_SLEEP_MILLIS 32+timeout/=TIMEOUT_SLEEP_MILLIS;+if(!timeout)+timeout=SCM_DEFAULT_TIMEOUT/TIMEOUT_SLEEP_MILLIS;++while(timeout-->0){+if(scm_admin_command_complete(scm_data))+return0;+msleep(TIMEOUT_SLEEP_MILLIS);+}++if(scm_admin_command_complete(scm_data))+return0;++return-EBUSY;+}++intscm_admin_response_handled(conststructscm_data*scm_data)+{+returnocxl_global_mmio_set64(scm_data->ocxl_afu,GLOBAL_MMIO_CHIC,+OCXL_LITTLE_ENDIAN,GLOBAL_MMIO_CHI_ACRA);+}++voidscm_warn_status(conststructscm_data*scm_data,constchar*message,+u8status)+{+constchar*text="Unknown";++switch(status){+caseSTATUS_SUCCESS:+text="Success";+break;++caseSTATUS_MEM_UNAVAILABLE:+text="Persistent memory unavailable";+break;++caseSTATUS_BAD_OPCODE:+text="Bad opcode";+break;++caseSTATUS_BAD_REQUEST_PARM:+text="Bad request parameter";+break;++caseSTATUS_BAD_DATA_PARM:+text="Bad data parameter";+break;++caseSTATUS_DEBUG_BLOCKED:+text="Debug action blocked";+break;++caseSTATUS_FAIL:+text="Failed";+break;+}++dev_warn(&scm_data->dev,"%s: %s (%x)\n",message,text,status);+}
From: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Date: 2020-02-03 14:18:53
On Tue, 3 Dec 2019 14:46:41 +1100
Alastair D'Silva [off-list ref] wrote:
From: Alastair D'Silva <redacted>
This patch requests the metadata required to issue admin commands, as well
as some helper functions to construct and check the completion of the
commands.
Signed-off-by: Alastair D'Silva <redacted>
On Mon, 2020-02-03 at 14:18 +0000, Jonathan Cameron wrote:
On Tue, 3 Dec 2019 14:46:41 +1100
Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
This patch requests the metadata required to issue admin commands,
as well
as some helper functions to construct and check the completion of
the
commands.
Signed-off-by: Alastair D'Silva <redacted>
*scm_data)
{
u64 chi = 0;
int rc = scm_chi(scm_data, &chi);
+ if (rc)
+ return false;
if (!(chi & GLOBAL_MMIO_CHI_CRDY)) {
dev_err(&scm_data->dev, "SCM controller is not
ready.\n");
@@ -502,6 +556,14 @@ static int scm_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
}
scm_data->pdev = pdev;
+ scm_data->timeouts[ADMIN_COMMAND_ERRLOG] = 2000; // ms
+ scm_data->timeouts[ADMIN_COMMAND_HEARTBEAT] = 100; // ms
+ scm_data->timeouts[ADMIN_COMMAND_SMART] = 100; // ms
+ scm_data->timeouts[ADMIN_COMMAND_CONTROLLER_DUMP] = 1000; // ms
+ scm_data->timeouts[ADMIN_COMMAND_CONTROLLER_STATS] = 100; // ms
+ scm_data->timeouts[ADMIN_COMMAND_SHUTDOWN] = 1000; // ms
+ scm_data->timeouts[ADMIN_COMMAND_FW_UPDATE] = 16000; // ms
+
pci_set_drvdata(pdev, scm_data);
scm_data->ocxl_fn = ocxl_function_open(pdev);
@@ -543,6 +605,11 @@ static int scm_probe(struct pci_dev *pdev,
op_code);
+
+/**
+ * scm_admin_response() - Validate an admin response
+ * @scm_data: a pointer to the SCM device data
+ * Returns the status code of the command, or negative on error
+ */
+int scm_admin_response(const struct scm_data *scm_data);
+
+/**
+ * scm_admin_command_execute() - Notify the controller to start
processing a pending admin command
+ * @scm_data: a pointer to the SCM device data
+ * Returns 0 on success, negative on error
+ */
+int scm_admin_command_execute(const struct scm_data *scm_data);
+
+/**
+ * scm_admin_command_complete_timeout() - Wait for an admin
command to finish executing
+ * @scm_data: a pointer to the SCM device data
+ * @command: the admin command to wait for completion (determines
the timeout)
+ * Returns 0 on success, -EBUSY on timeout
+ */
+int scm_admin_command_complete_timeout(const struct scm_data
*scm_data,
+ int command);
+
+/**
+ * scm_admin_response_handled() - Notify the controller that the
admin response has been handled
+ * @scm_data: a pointer to the SCM device data
+ * Returns 0 on success, negative on failure
+ */
+int scm_admin_response_handled(const struct scm_data *scm_data);
+
+/**
+ * scm_warn_status() - Emit a kernel warning showing a command
status.
+ * @scm_data: a pointer to the SCM device data
+ * @message: A message to accompany the warning
+ * @status: The command status
+ */
+void scm_warn_status(const struct scm_data *scm_data, const char
*message,
+ u8 status);
--
Alastair D'Silva
Open Source Developer
Linux Technology Centre, IBM Australia
mob: 0423 762 819
From: Alastair D'Silva <redacted>
This patch adds IOCTLs to allow userspace to request & fetch dumps
of the internal controller state.
This is useful during debugging or when a fatal error on the controller
has occurred.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 132 +++++++++++++++++++++++++++++++++
include/uapi/nvdimm/ocxl-scm.h | 15 ++++
2 files changed, 147 insertions(+)
@@ -6,6 +6,28 @@#include<linux/types.h>#include<linux/ioctl.h>+enumscm_fwdebug_action{+SCM_FWDEBUG_READ_CONTROLLER_MEMORY=0x01,+SCM_FWDEBUG_WRITE_CONTROLLER_MEMORY=0x02,+SCM_FWDEBUG_ENABLE_FUNCTION=0x03,+SCM_FWDEBUG_DISABLE_FUNCTION=0x04,+SCM_FWDEBUG_GET_PEL=0x05,// Retrieve Persistent Error Log+};++structscm_ioctl_buffer_info{+__u32admin_command_buffer_size;// out+__u32near_storage_buffer_size;// out+};++structscm_ioctl_fwdebug{// All args are inputs+enumscm_fwdebug_actiondebug_action;+__u16function_code;+__u16buf_size;// Size of optional data buffer+__u64debug_parameter_1;+__u64debug_parameter_2;+__u8*buf;// Pointer to optional in/out data buffer+};+#define SCM_ERROR_LOG_ACTION_RESET (1 << (32-32))#define SCM_ERROR_LOG_ACTION_CHKFW (1 << (53-32))#define SCM_ERROR_LOG_ACTION_REPLACE (1 << (54-32))
@@ -66,6 +88,11 @@ struct scm_ioctl_controller_stats {__u64cache_write_latency;// nanoseconds};+structscm_ioctl_mmio{+__u64address;// Offset in global MMIO space+__u64val;// value to write/was read+};+structscm_ioctl_eventfd{__s32eventfd;__u32reserved;
@@ -0,0 +1,163 @@+// SPDX-License-Identifier: GPL-2.0++// Copyright 2018 IBM Corp.++#include<linux/sysfs.h>+#include<linux/capability.h>+#include<linux/limits.h>+#include<linux/firmware.h>+#include"scm_internal.h"++staticssize_tfw_version_show(structdevice*device,+structdevice_attribute*attr,char*buf)+{+structscm_data*scm_data=container_of(device,structscm_data,dev);++returnscnprintf(buf,PAGE_SIZE,"%s\n",scm_data->fw_version);+}++#define SCM_FWUPDATE_BLOCK_SIZE 32768++/**+*scm_update_firmware()-Writea32kBblockofdatatofirmware+*Theblockmaybelessthan32kBifitisthelastone+*+*scm_datatheSCMdevicemetadata+*offset:theoffsetofthestartoftheblock+*buf:theblockdata+*size:thesizeoftheblock+*/+staticssize_tscm_update_firmware(structscm_data*scm_data,size_toffset,+constchar*buf,size_tsize)+{+intrc;+size_ti;+u64val;++if(size>SCM_FWUPDATE_BLOCK_SIZE)+return-EINVAL;++rc=scm_admin_command_request(scm_data,ADMIN_COMMAND_FW_UPDATE);+if(rc)+returnrc;++val=(((u64)offset)<<32)|size;+rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,+scm_data->admin_command.request_offset+8,+OCXL_LITTLE_ENDIAN,val);+if(rc)+returnrc;++for(i=0;i<size;i+=8){+val=*(u64*)(buf+i);+rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+i,+OCXL_HOST_ENDIAN,val);+if(rc)+returnrc;+}++rc=scm_admin_command_execute(scm_data);+if(rc)+returnrc;++rc=scm_admin_command_complete_timeout(scm_data,+ADMIN_COMMAND_FW_UPDATE);+if(rc<0){+dev_err(&scm_data->dev,"Firmware update timeout\n");+returnrc;+}++rc=scm_admin_response(scm_data);+if(rc<0)+returnrc;+if(rc!=STATUS_SUCCESS){+scm_warn_status_fw_update(scm_data,"FW Update",rc);+returnrc;+}++return0;+}++/*+*Parseoutafirmwarefilenamefromsysfs,retrieveit'scontentsandwriteit+*totheSCMdevicefirmwarestorage+*/+staticssize_tfw_update_filename_store(structdevice*device,+structdevice_attribute*attr,+constchar*buf,size_tsize)+{+charpath[NAME_MAX+1];+constchar*end;+conststructfirmware*firmware=NULL;+size_toffset;+intrc;+structscm_data*scm_data=container_of(device,structscm_data,dev);++if(!capable(CAP_SYS_ADMIN))+return-EACCES;++end=strnchr(buf,size,'\n');+if(end==NULL)+end=buf+strnlen(buf,size);++if((end-buf)>NAME_MAX){+dev_err(device,"Firmware filename '%-.*s' too long\n",+(int)(end-buf),buf);+return-EIO;+}++memcpy(path,buf,end-buf);+path[end-buf]='\0';++if(request_firmware(&firmware,path,device)){+dev_err(device,"Firmware file %s not found\n",path);+return-EIO;+}++if(firmware->size%8){+release_firmware(firmware);+dev_err(device,"Firmware '%s' should be a multiple of 8 bytes",path);+return-EINVAL;+}++mutex_lock(&scm_data->admin_command.lock);++for(offset=0;offset<firmware->size;offset+=SCM_FWUPDATE_BLOCK_SIZE){+size_tremainder=firmware->size-offset;+size_tblock_size;++block_size=(remainder>SCM_FWUPDATE_BLOCK_SIZE)?+SCM_FWUPDATE_BLOCK_SIZE:remainder;+rc=scm_update_firmware(scm_data,offset,+firmware->data+offset,block_size);+if(rc){+mutex_unlock(&scm_data->admin_command.lock);+return-EFAULT;+}+}++mutex_unlock(&scm_data->admin_command.lock);++returnsize;+}++staticstructdevice_attributescm_attrs[]={+__ATTR_RO(fw_version),+__ATTR_WO(fw_update_filename),+};++intscm_sysfs_add(structscm_data*scm_data)+{+inti,rc;++for(i=0;i<ARRAY_SIZE(scm_attrs);i++){+rc=device_create_file(&scm_data->dev,&scm_attrs[i]);+if(rc){+for(;--i>=0;)+device_remove_file(&scm_data->dev,&scm_attrs[i]);++returnrc;+}+}+return0;+}
From: Alastair D'Silva <redacted>
When health & performance data is requested from the controller,
it responds with an error log containing the requested information.
This patch allows the request to me issued via an IOCTL.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 16 ++++++++++++++++
include/uapi/nvdimm/ocxl-scm.h | 1 +
2 files changed, 17 insertions(+)
From: Alastair D'Silva <redacted>
Some of the interrupts that the card generates are better handled
by the userspace daemon, in particular:
Controller Hardware/Firmware Fatal
Controller Dump Available
Error Log available
This patch allows a userspace application to register an eventfd with
the driver via SCM_IOCTL_EVENTFD to receive notifications of these
interrupts.
Userspace can then identify what events have occurred by calling
SCM_IOCTL_EVENT_CHECK and checking against the SCM_IOCTL_EVENT_FOO
masks.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 216 +++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 5 +
include/uapi/nvdimm/ocxl-scm.h | 16 +++
3 files changed, 237 insertions(+)
@@ -986,6 +1003,51 @@ static int scm_ioctl_controller_stats(struct scm_data *scm_data,returnrc;}+staticintscm_ioctl_eventfd(structscm_data*scm_data,+structscm_ioctl_eventfd__user*uarg)+{+structscm_ioctl_eventfdargs;++if(copy_from_user(&args,uarg,sizeof(args)))+return-EFAULT;++if(scm_data->ev_ctx)+return-EINVAL;++scm_data->ev_ctx=eventfd_ctx_fdget(args.eventfd);+if(!scm_data->ev_ctx)+return-EFAULT;++return0;+}++staticintscm_ioctl_event_check(structscm_data*scm_data,u64__user*uarg)+{+u64val=0;+intrc;+u64chi=0;++rc=scm_chi(scm_data,&chi);+if(rc<0)+returnrc;++if(chi&GLOBAL_MMIO_CHI_ELA)+val|=SCM_IOCTL_EVENT_ERROR_LOG_AVAILABLE;++if(chi&GLOBAL_MMIO_CHI_CDA)+val|=SCM_IOCTL_EVENT_CONTROLLER_DUMP_AVAILABLE;++if(chi&GLOBAL_MMIO_CHI_CFFS)+val|=SCM_IOCTL_EVENT_FIRMWARE_FATAL;++if(chi&GLOBAL_MMIO_CHI_CHFS)+val|=SCM_IOCTL_EVENT_HARDWARE_FATAL;++rc=copy_to_user((u64__user*)uarg,&val,sizeof(val));++returnrc;+}+staticlongscm_file_ioctl(structfile*file,unsignedintcmd,unsignedlongargs){
@@ -1015,6 +1077,15 @@ static long scm_file_ioctl(struct file *file, unsigned int cmd,rc=scm_ioctl_controller_stats(scm_data,(structscm_ioctl_controller_stats__user*)args);break;++caseSCM_IOCTL_EVENTFD:+rc=scm_ioctl_eventfd(scm_data,+(structscm_ioctl_eventfd__user*)args);+break;++caseSCM_IOCTL_EVENT_CHECK:+rc=scm_ioctl_event_check(scm_data,(u64__user*)args);+break;}returnrc;
@@ -1156,6 +1227,146 @@ static void scm_dump_error_log(struct scm_data *scm_data)kfree(buf);}+staticirqreturn_tscm_imn0_handler(void*private)+{+structscm_data*scm_data=private;+u64chi=0;++(void)scm_chi(scm_data,&chi);++if(chi&GLOBAL_MMIO_CHI_ELA){+dev_warn(&scm_data->dev,"Error log is available\n");++if(scm_data->ev_ctx)+eventfd_signal(scm_data->ev_ctx,1);+}++if(chi&GLOBAL_MMIO_CHI_CDA){+dev_warn(&scm_data->dev,"Controller dump is available\n");++if(scm_data->ev_ctx)+eventfd_signal(scm_data->ev_ctx,1);+}+++returnIRQ_HANDLED;+}++staticirqreturn_tscm_imn1_handler(void*private)+{+structscm_data*scm_data=private;+u64chi=0;++(void)scm_chi(scm_data,&chi);++if(chi&(GLOBAL_MMIO_CHI_CFFS|GLOBAL_MMIO_CHI_CHFS)){+dev_err(&scm_data->dev,+"Controller status is fatal, chi=0x%llx, going offline\n",chi);++if(scm_data->nvdimm_bus){+nvdimm_bus_unregister(scm_data->nvdimm_bus);+scm_data->nvdimm_bus=NULL;+}++if(scm_data->ev_ctx)+eventfd_signal(scm_data->ev_ctx,1);+}++returnIRQ_HANDLED;+}+++/**+*scm_setup_irq()-SetuptheIRQsfortheSCMdevice+*@scm_data:theSCMmetadata+*Return:0onsuccess,negativeonfailure+*/+staticintscm_setup_irq(structscm_data*scm_data)+{+intrc;+u64irq_addr;++rc=ocxl_afu_irq_alloc(scm_data->ocxl_context,&scm_data->irq_id[0]);+if(rc)+returnrc;++rc=ocxl_irq_set_handler(scm_data->ocxl_context,scm_data->irq_id[0],+scm_imn0_handler,NULL,scm_data);++irq_addr=ocxl_afu_irq_get_addr(scm_data->ocxl_context,scm_data->irq_id[0]);+if(!irq_addr)+return-EINVAL;++scm_data->irq_addr[0]=ioremap(irq_addr,PAGE_SIZE);+if(!scm_data->irq_addr[0])+return-EINVAL;++rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,GLOBAL_MMIO_IMA0_OHP,+OCXL_LITTLE_ENDIAN,+(u64)scm_data->irq_addr[0]);+if(rc)+gotoout_irq0;++rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,GLOBAL_MMIO_IMA0_CFP,+OCXL_LITTLE_ENDIAN,0);+if(rc)+gotoout_irq0;++rc=ocxl_afu_irq_alloc(scm_data->ocxl_context,&scm_data->irq_id[1]);+if(rc)+gotoout_irq0;+++rc=ocxl_irq_set_handler(scm_data->ocxl_context,scm_data->irq_id[1],+scm_imn1_handler,NULL,scm_data);+if(rc)+gotoout_irq0;++irq_addr=ocxl_afu_irq_get_addr(scm_data->ocxl_context,scm_data->irq_id[1]);+if(!irq_addr){+rc=-EFAULT;+gotoout_irq0;+}++scm_data->irq_addr[1]=ioremap(irq_addr,PAGE_SIZE);+if(!scm_data->irq_addr[1]){+rc=-EINVAL;+gotoout_irq0;+}++rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,GLOBAL_MMIO_IMA1_OHP,+OCXL_LITTLE_ENDIAN,+(u64)scm_data->irq_addr[1]);+if(rc)+gotoout_irq1;++rc=ocxl_global_mmio_write64(scm_data->ocxl_afu,GLOBAL_MMIO_IMA1_CFP,+OCXL_LITTLE_ENDIAN,0);+if(rc)+gotoout_irq1;++// Enable doorbells+rc=ocxl_global_mmio_set64(scm_data->ocxl_afu,GLOBAL_MMIO_CHIE,+OCXL_LITTLE_ENDIAN,+GLOBAL_MMIO_CHI_ELA|GLOBAL_MMIO_CHI_CDA|+GLOBAL_MMIO_CHI_CFFS|GLOBAL_MMIO_CHI_CHFS|+GLOBAL_MMIO_CHI_NSCRA);+if(rc)+gotoout_irq1;++return0;++out_irq1:+iounmap(scm_data->irq_addr[1]);+scm_data->irq_addr[1]=NULL;++out_irq0:+iounmap(scm_data->irq_addr[0]);+scm_data->irq_addr[0]=NULL;++returnrc;+}+/***scm_probe_function_0-Setupfunction0foranOpenCAPIStorageClassMemorydevice*Thisisimportantasitenablestemplateshigherthan0acrossallotherfunctions,
@@ -1261,6 +1472,11 @@ static int scm_probe(struct pci_dev *pdev, const struct pci_device_id *ent)gotoerr;}+if(scm_setup_irq(scm_data)){+dev_err(&pdev->dev,"Could not set up OCXL IRQs for SCM\n");+gotoerr;+}+if(scm_setup_command_metadata(scm_data)){dev_err(&pdev->dev,"Could not read OCXL command matada\n");gotoerr;
@@ -32,5 +32,7 @@ extern int pnv_ocxl_spa_remove_pe_from_cache(void *platform_data, int pe_handle)externintpnv_ocxl_alloc_xive_irq(u32*irq,u64*trigger_addr);externvoidpnv_ocxl_free_xive_irq(u32irq);+externu64pnv_ocxl_platform_lpc_setup(structpci_dev*pdev,u64size);+externvoidpnv_ocxl_platform_lpc_release(structpci_dev*pdev);
nit: I don't think these need to be extern?
--
Andrew Donnellan OzLabs, ADL Canberra
ajd@linux.ibm.com IBM Australia Limited
@@ -32,5 +32,7 @@ extern int pnv_ocxl_spa_remove_pe_from_cache(void *platform_data, int pe_handle)externintpnv_ocxl_alloc_xive_irq(u32*irq,u64*trigger_addr);externvoidpnv_ocxl_free_xive_irq(u32irq);+externu64pnv_ocxl_platform_lpc_setup(structpci_dev*pdev,u64size);+externvoidpnv_ocxl_platform_lpc_release(structpci_dev*pdev);
nit: I don't think these need to be extern?
And even if they were, as verified by checkpatch:
"extern prototypes should be avoided in .h files"
check_hotplug_memory_addressable() is only declared if
CONFIG_MEMORY_HOTPLUG_SPARSE is selected.
I think we also need a #ifdef here.
Agreed. I think that since any actual use of the memory is going to be
dependant on both hotplug & sparse, moving the ifdef to wrap the
functions & declarations makes sense.
From: Alastair D'Silva <redacted>
This patch introduces a character device (/dev/ocxl-scmX) which further
patches will use to interact with userspace.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 114 ++++++++++++++++++++++++++++-
drivers/nvdimm/ocxl/scm_internal.h | 2 +
2 files changed, 115 insertions(+), 1 deletion(-)
From: Alastair D'Silva <redacted>
The near storage command 'Secure Erase' overwrites all data on the
media.
This patch hooks it up to the security function 'overwrite'.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 164 ++++++++++++++++++++++++++++-
drivers/nvdimm/ocxl/scm_internal.c | 1 +
drivers/nvdimm/ocxl/scm_internal.h | 17 +++
3 files changed, 180 insertions(+), 2 deletions(-)
From: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Date: 2020-02-03 15:10:28
On Tue, 3 Dec 2019 14:46:52 +1100
Alastair D'Silva [off-list ref] wrote:
From: Alastair D'Silva <redacted>
The near storage command 'Secure Erase' overwrites all data on the
media.
This patch hooks it up to the security function 'overwrite'.
Signed-off-by: Alastair D'Silva <redacted>
Feels likely that we are going to end up with quite a few blocks like this as
the driver is extended. Perhaps just start out with a switch statement and
separate functions that it calls?
On Mon, 2020-02-03 at 15:10 +0000, Jonathan Cameron wrote:
On Tue, 3 Dec 2019 14:46:52 +1100
Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
The near storage command 'Secure Erase' overwrites all data on the
media.
This patch hooks it up to the security function 'overwrite'.
Signed-off-by: Alastair D'Silva <redacted>
scm_data *scm_data)
kfree(buf);
}
+static void scm_handle_nscra_doorbell(struct scm_data *scm_data)
+{
+ int rc;
+
+ if (scm_data->ns_command.op_code == NS_COMMAND_SECURE_ERASE) {
Feels likely that we are going to end up with quite a few blocks like
this as
the driver is extended. Perhaps just start out with a switch
statement and
separate functions that it calls?
At the moment, this is the only near storage command documented on the
device, and I don't think there will be any more.
quoted
+ u64 success, attempted;
+
One is enough here.
It's not, there is a comparison between them later.
From: Alastair D'Silva <redacted>
This driver exposes LPC memory on OpenCAPI SCM cards
as an NVDIMM, allowing the existing nvram infrastructure
to be used.
Namespace metadata is stored on the media itself, so
scm_reserve_metadata() maps 1 section's worth of PMEM storage
at the start to hold this. The rest of the PMEM range is registered
with libnvdimm as an nvdimm. scm_ndctl_config_read/write/size() provide
callbacks to libnvdimm to access the metadata.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/Kconfig | 2 +
drivers/nvdimm/Makefile | 2 +-
drivers/nvdimm/ocxl/Kconfig | 15 +
drivers/nvdimm/ocxl/Makefile | 7 +
drivers/nvdimm/ocxl/scm.c | 519 +++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 28 ++
6 files changed, 572 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvdimm/ocxl/Kconfig
create mode 100644 drivers/nvdimm/ocxl/Makefile
create mode 100644 drivers/nvdimm/ocxl/scm.c
create mode 100644 drivers/nvdimm/ocxl/scm_internal.h
@@ -0,0 +1,519 @@+// SPDX-License-Identifier: GPL-2.0++// Copyright 2019 IBM Corp.++/*+*AdriverforStorageClassMemory,connectedviaOpenCAPI+*/++#include<linux/module.h>+#include<misc/ocxl.h>+#include<linux/ndctl.h>+#include<linux/mm_types.h>+#include<linux/memory_hotplug.h>+#include"scm_internal.h"+++staticconststructpci_device_idscm_pci_tbl[]={+{PCI_DEVICE(PCI_VENDOR_ID_IBM,0x0625),},+{}+};++MODULE_DEVICE_TABLE(pci,scm_pci_tbl);++#define SCM_NUM_MINORS 256 // Total to reserve++staticdev_tscm_dev;+staticstructclass*scm_class;+staticstructmutexminors_idr_lock;+staticstructidrminors_idr;++staticconststructattribute_group*scm_pmem_attribute_groups[]={+&nvdimm_bus_attribute_group,+NULL,+};++staticconststructattribute_group*scm_pmem_region_attribute_groups[]={+&nd_region_attribute_group,+&nd_device_attribute_group,+&nd_mapping_attribute_group,+&nd_numa_attribute_group,+NULL,+};++/**+*scm_ndctl_config_write()-HandleaND_CMD_SET_CONFIG_DATAcommandfromndctl+*@scm_data:theSCMmetadata+*@command:theincomingdatatowrite+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_write(structscm_data*scm_data,+structnd_cmd_set_config_hdr*command)+{+if(command->in_offset+command->in_length>SCM_LABEL_AREA_SIZE)+return-EINVAL;++memcpy_flushcache(scm_data->metadata_addr+command->in_offset,command->in_buf,+command->in_length);++return0;+}++/**+*scm_ndctl_config_read()-HandleaND_CMD_GET_CONFIG_DATAcommandfromndctl+*@scm_data:theSCMmetadata+*@command:thereadrequest+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_read(structscm_data*scm_data,+structnd_cmd_get_config_data_hdr*command)+{+if(command->in_offset+command->in_length>SCM_LABEL_AREA_SIZE)+return-EINVAL;++memcpy_mcsafe(command->out_buf,scm_data->metadata_addr+command->in_offset,+command->in_length);++return0;+}++/**+*scm_ndctl_config_size()-HandleaND_CMD_GET_CONFIG_SIZEcommandfromndctl+*@scm_data:theSCMmetadata+*@command:thereadrequest+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_size(structnd_cmd_get_config_size*command)+{+command->status=0;+command->config_size=SCM_LABEL_AREA_SIZE;+command->max_xfer=PAGE_SIZE;++return0;+}++staticintscm_ndctl(structnvdimm_bus_descriptor*nd_desc,+structnvdimm*nvdimm,+unsignedintcmd,void*buf,unsignedintbuf_len,int*cmd_rc)+{+structscm_data*scm_data=container_of(nd_desc,structscm_data,bus_desc);++switch(cmd){+caseND_CMD_GET_CONFIG_SIZE:+*cmd_rc=scm_ndctl_config_size(buf);+return0;++caseND_CMD_GET_CONFIG_DATA:+*cmd_rc=scm_ndctl_config_read(scm_data,buf);+return0;++caseND_CMD_SET_CONFIG_DATA:+*cmd_rc=scm_ndctl_config_write(scm_data,buf);+return0;++default:+return-ENOTTY;+}+}++staticssize_tserial_show(structdevice*dev,+structdevice_attribute*attr,char*buf)+{+structnvdimm*nvdimm=to_nvdimm(dev);+structscm_data*scm_data=nvdimm_provider_data(nvdimm);+conststructocxl_fn_config*config=ocxl_function_config(scm_data->ocxl_fn);++returnsprintf(buf,"0x%llx\n",config->serial);+}+staticDEVICE_ATTR_RO(serial);++staticstructattribute*scm_dimm_attributes[]={+&dev_attr_serial.attr,+NULL,+};++staticumode_tscm_dimm_attr_visible(structkobject*kobj,+structattribute*a,intn)+{+returna->mode;+}++staticconststructattribute_groupscm_dimm_attribute_group={+.name="ocxl",+.attrs=scm_dimm_attributes,+.is_visible=scm_dimm_attr_visible,+};++staticconststructattribute_group*scm_dimm_attribute_groups[]={+&nvdimm_attribute_group,+&nd_device_attribute_group,+&scm_dimm_attribute_group,+NULL,+};++/**+*scm_reserve_metadata()-Reservespacefornvdimmmetadata+*@scm_data:TheSCMdevicedata+*@lpc_mem:TheresourcerepresentingtheLPCmemoryoftheSCMdevice+*/+staticintscm_reserve_metadata(structscm_data*scm_data,+structresource*lpc_mem)+{+scm_data->metadata_addr=devm_memremap(&scm_data->dev,lpc_mem->start,+SCM_LABEL_AREA_SIZE,MEMREMAP_WB);+if(IS_ERR(scm_data->metadata_addr))+returnPTR_ERR(scm_data->metadata_addr);++return0;+}++/**+*scm_register_lpc_mem()-DiscoverpersistentmemoryonadeviceandregisteritwiththeNVDIMMsubsystem+*@scm_data:TheSCMdevicedata+*Return:0onsuccess+*/+staticintscm_register_lpc_mem(structscm_data*scm_data)+{+structnd_region_descregion_desc;+structnd_mapping_descnd_mapping_desc;+structresource*lpc_mem;+conststructocxl_afu_config*config;+conststructocxl_fn_config*fn_config;+intrc;+unsignedlongnvdimm_cmd_mask=0;+unsignedlongnvdimm_flags=0;+inttarget_node;+charserial[16+1];++// Set up the reserved metadata area+rc=ocxl_afu_map_lpc_mem(scm_data->ocxl_afu);+if(rc<0)+returnrc;++lpc_mem=ocxl_afu_lpc_mem(scm_data->ocxl_afu);+if(lpc_mem==NULL||lpc_mem->start==0)+return-EINVAL;++config=ocxl_afu_config(scm_data->ocxl_afu);+fn_config=ocxl_function_config(scm_data->ocxl_fn);++rc=scm_reserve_metadata(scm_data,lpc_mem);+if(rc)+returnrc;++scm_data->bus_desc.attr_groups=scm_pmem_attribute_groups;+scm_data->bus_desc.provider_name="ocxl-scm";+scm_data->bus_desc.ndctl=scm_ndctl;+scm_data->bus_desc.module=THIS_MODULE;++scm_data->nvdimm_bus=nvdimm_bus_register(&scm_data->dev,+&scm_data->bus_desc);+if(!scm_data->nvdimm_bus)+return-EINVAL;++scm_data->scm_res.start=(u64)lpc_mem->start+SCM_LABEL_AREA_SIZE;+scm_data->scm_res.end=(u64)lpc_mem->start+config->lpc_mem_size-1;+scm_data->scm_res.name="SCM persistent memory";++set_bit(ND_CMD_GET_CONFIG_SIZE,&nvdimm_cmd_mask);+set_bit(ND_CMD_GET_CONFIG_DATA,&nvdimm_cmd_mask);+set_bit(ND_CMD_SET_CONFIG_DATA,&nvdimm_cmd_mask);++set_bit(NDD_ALIASING,&nvdimm_flags);++snprintf(serial,sizeof(serial),"%llx",fn_config->serial);+nd_mapping_desc.nvdimm=nvdimm_create(scm_data->nvdimm_bus,scm_data,+scm_dimm_attribute_groups,+nvdimm_flags,nvdimm_cmd_mask,+0,NULL);+if(!nd_mapping_desc.nvdimm)+return-ENOMEM;++if(nvdimm_bus_check_dimm_count(scm_data->nvdimm_bus,1))+return-EINVAL;++nd_mapping_desc.start=scm_data->scm_res.start;+nd_mapping_desc.size=resource_size(&scm_data->scm_res);+nd_mapping_desc.position=0;++scm_data->nd_set.cookie1=fn_config->serial+1;// allow for empty serial+scm_data->nd_set.cookie2=fn_config->serial+1;++target_node=of_node_to_nid(scm_data->pdev->dev.of_node);++memset(®ion_desc,0,sizeof(region_desc));+region_desc.res=&scm_data->scm_res;+region_desc.attr_groups=scm_pmem_region_attribute_groups;+region_desc.numa_node=NUMA_NO_NODE;+region_desc.target_node=target_node;+region_desc.num_mappings=1;+region_desc.mapping=&nd_mapping_desc;+region_desc.nd_set=&scm_data->nd_set;++set_bit(ND_REGION_PAGEMAP,®ion_desc.flags);+/*+*NB:libnvdimmcopiesthedatafromndr_descintoit'sown+*structuressopassingastackpointerisfine.+*/+scm_data->nd_region=nvdimm_pmem_region_create(scm_data->nvdimm_bus,+®ion_desc);+if(!scm_data->nd_region)+return-EINVAL;++dev_info(&scm_data->dev,+"Onlining %lluMB of persistent memory\n",+nd_mapping_desc.size/SZ_1M);++return0;+}++/**+*allocate_scm_minor()-AllocateaminornumbertouseforanSCMdevice+*@scm_data:TheSCMdevicetoassociatetheminorwith+*Return:theallocatedminornumber+*/+staticintallocate_scm_minor(structscm_data*scm_data)+{+intminor;++mutex_lock(&minors_idr_lock);+minor=idr_alloc(&minors_idr,scm_data,0,SCM_NUM_MINORS,GFP_KERNEL);+mutex_unlock(&minors_idr_lock);+returnminor;+}++staticvoidfree_scm_minor(structscm_data*scm_data)+{+mutex_lock(&minors_idr_lock);+idr_remove(&minors_idr,MINOR(scm_data->dev.devt));+mutex_unlock(&minors_idr_lock);+}++/**+*free_scm()-FreeallmembersofanSCMstruct+*@scm_data:theSCMmetadatatoclear+*/+staticvoidfree_scm(structscm_data*scm_data)+{+intrc;++if(scm_data->nvdimm_bus)+nvdimm_bus_unregister(scm_data->nvdimm_bus);++free_scm_minor(scm_data);++if(scm_data->metadata_addr)+devm_memunmap(&scm_data->dev,scm_data->metadata_addr);++if(scm_data->ocxl_context){+rc=ocxl_context_detach(scm_data->ocxl_context);+if(rc==-EBUSY)+dev_warn(&scm_data->dev,"Timeout detaching ocxl context\n");+else+ocxl_context_free(scm_data->ocxl_context);++}++if(scm_data->ocxl_afu)+ocxl_afu_put(scm_data->ocxl_afu);++if(scm_data->ocxl_fn)+ocxl_function_close(scm_data->ocxl_fn);++kfree(scm_data);+}++/**+*free_scm_dev-FreeanSCMdevice+*@dev:Thedevicestruct+*/+staticvoidfree_scm_dev(structdevice*dev)+{+structscm_data*scm_data=container_of(dev,structscm_data,dev);++free_scm(scm_data);+}++/**+*scm_register-RegisteranSCMdevicewiththekernel+*@scm_data:theSCMmetadata+*Return:0onsuccess,negativeonfailure+*/+staticintscm_register(structscm_data*scm_data)+{+intrc;+intminor=allocate_scm_minor(scm_data);++if(minor<0)+returnminor;++scm_data->dev.release=free_scm_dev;+rc=dev_set_name(&scm_data->dev,"ocxl-scm%d",minor);+if(rc<0)+returnrc;++scm_data->dev.devt=MKDEV(MAJOR(scm_dev),minor);+scm_data->dev.class=scm_class;+scm_data->dev.parent=&scm_data->pdev->dev;++rc=device_register(&scm_data->dev);+returnrc;+}++/**+*scm_remove()-FreeanOpenCAPIStorageClassMemorydevice+*@pdev:thePCIdeviceinformationstruct+*/+staticvoidscm_remove(structpci_dev*pdev)+{+if(PCI_FUNC(pdev->devfn)==0){+structscm_function_0*scm_func_0=pci_get_drvdata(pdev);++if(scm_func_0){+ocxl_function_close(scm_func_0->ocxl_fn);+scm_func_0->ocxl_fn=NULL;+}+}else{+structscm_data*scm_data=pci_get_drvdata(pdev);++if(scm_data)+device_unregister(&scm_data->dev);+}+}++/**+*scm_probe_function_0-Setupfunction0foranOpenCAPIStorageClassMemorydevice+*Thisisimportantasitenablestemplateshigherthan0acrossallotherfunctions,+*whichinturnenableshigherbandwidthaccesses+*whichinturnenableshigherbandwidthaccesses+*@pdev:thePCIdeviceinformationstruct+*Return:0onsuccess,negativeonfailure+*/+staticintscm_probe_function_0(structpci_dev*pdev)+{+structscm_function_0*scm_func_0=NULL;+structocxl_fn*fn;++scm_func_0=kzalloc(sizeof(*scm_func_0),GFP_KERNEL);+if(!scm_func_0)+return-ENOMEM;++scm_func_0->pdev=pdev;+fn=ocxl_function_open(pdev);+if(IS_ERR(fn)){+kfree(scm_func_0);+dev_err(&pdev->dev,"failed to open OCXL function\n");+returnPTR_ERR(fn);+}+scm_func_0->ocxl_fn=fn;++pci_set_drvdata(pdev,scm_func_0);++return0;+}++/**+*scm_probe-InitanOpenCAPIStorageClassMemorydevice+*@pdev:thePCIdeviceinformationstruct+*@ent:Theentryfromscm_pci_tbl+*Return:0onsuccess,negativeonfailure+*/+staticintscm_probe(structpci_dev*pdev,conststructpci_device_id*ent)+{+structscm_data*scm_data=NULL;++if(PCI_FUNC(pdev->devfn)==0)+returnscm_probe_function_0(pdev);+elseif(PCI_FUNC(pdev->devfn)!=1)+return0;++scm_data=kzalloc(sizeof(*scm_data),GFP_KERNEL);+if(!scm_data){+dev_err(&pdev->dev,"Could not allocate SCM metadata\n");+gotoerr;+}+scm_data->pdev=pdev;++pci_set_drvdata(pdev,scm_data);++scm_data->ocxl_fn=ocxl_function_open(pdev);+if(IS_ERR(scm_data->ocxl_fn)){+kfree(scm_data);+scm_data=NULL;+pci_set_drvdata(pdev,NULL);+dev_err(&pdev->dev,"failed to open OCXL function\n");+gotoerr;+}++scm_data->ocxl_afu=ocxl_function_fetch_afu(scm_data->ocxl_fn,0);+if(scm_data->ocxl_afu==NULL){+dev_err(&pdev->dev,"Could not get OCXL AFU from function\n");+gotoerr;+}++ocxl_afu_get(scm_data->ocxl_afu);++if(scm_register(scm_data)<0){+dev_err(&pdev->dev,"Could not register SCM device with the kernel\n");+gotoerr;+}++// Resources allocated below here are cleaned up in the release handler++if(ocxl_context_alloc(&scm_data->ocxl_context,scm_data->ocxl_afu,NULL)){+dev_err(&pdev->dev,"Could not allocate OCXL context\n");+gotoerr;+}++if(ocxl_context_attach(scm_data->ocxl_context,0,NULL)){+dev_err(&pdev->dev,"Could not attach ocxl context\n");+gotoerr;+}++if(scm_register_lpc_mem(scm_data)){+dev_err(&pdev->dev,"Could not register OCXL SCM memory with libnvdimm\n");+gotoerr;+}++return0;++err:+/*+*Furthercleanupisdoneinthereleasehandlerviafree_scm()+*ThisallowsustokeepthecharacterdevicelivetohandleIOCTLsto+*investigateissuesifthecardhasanerror+*/++dev_err(&pdev->dev,+"Error detected, will not register storage class memory\n");+return-ENXIO;+}++staticstructpci_driverscm_pci_driver={+.name="ocxl-scm",+.id_table=scm_pci_tbl,+.probe=scm_probe,+.remove=scm_remove,+.shutdown=scm_remove,+};++staticint__initscm_init(void)+{+intrc=0;++rc=pci_register_driver(&scm_pci_driver);+if(rc)+returnrc;++return0;+}++staticvoidscm_exit(void)+{+pci_unregister_driver(&scm_pci_driver);+}++module_init(scm_init);+module_exit(scm_exit);++MODULE_DESCRIPTION("Storage Class Memory");+MODULE_LICENSE("GPL");
On Tue, 2019-12-03 at 14:46 +1100, Alastair D'Silva wrote:
quoted hunk
From: Alastair D'Silva <redacted>
This driver exposes LPC memory on OpenCAPI SCM cards
as an NVDIMM, allowing the existing nvram infrastructure
to be used.
Namespace metadata is stored on the media itself, so
scm_reserve_metadata() maps 1 section's worth of PMEM storage
at the start to hold this. The rest of the PMEM range is registered
with libnvdimm as an nvdimm. scm_ndctl_config_read/write/size()
provide
callbacks to libnvdimm to access the metadata.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/Kconfig | 2 +
drivers/nvdimm/Makefile | 2 +-
drivers/nvdimm/ocxl/Kconfig | 15 +
drivers/nvdimm/ocxl/Makefile | 7 +
drivers/nvdimm/ocxl/scm.c | 519
+++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 28 ++
6 files changed, 572 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvdimm/ocxl/Kconfig
create mode 100644 drivers/nvdimm/ocxl/Makefile
create mode 100644 drivers/nvdimm/ocxl/scm.c
create mode 100644 drivers/nvdimm/ocxl/scm_internal.h
From: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Date: 2020-02-03 13:20:49
On Tue, 3 Dec 2019 14:46:38 +1100
Alastair D'Silva [off-list ref] wrote:
From: Alastair D'Silva <redacted>
This driver exposes LPC memory on OpenCAPI SCM cards
as an NVDIMM, allowing the existing nvram infrastructure
to be used.
Namespace metadata is stored on the media itself, so
scm_reserve_metadata() maps 1 section's worth of PMEM storage
at the start to hold this. The rest of the PMEM range is registered
with libnvdimm as an nvdimm. scm_ndctl_config_read/write/size() provide
callbacks to libnvdimm to access the metadata.
Signed-off-by: Alastair D'Silva <redacted>
Hi Alastair,
A few bits and bobs inline.
Thanks,
Jonathan
@@ -0,0 +1,519 @@+// SPDX-License-Identifier: GPL-2.0++// Copyright 2019 IBM Corp.++/*+*AdriverforStorageClassMemory,connectedviaOpenCAPI+*/++#include<linux/module.h>+#include<misc/ocxl.h>+#include<linux/ndctl.h>+#include<linux/mm_types.h>+#include<linux/memory_hotplug.h>+#include"scm_internal.h"+++staticconststructpci_device_idscm_pci_tbl[]={+{PCI_DEVICE(PCI_VENDOR_ID_IBM,0x0625),},+{}+};++MODULE_DEVICE_TABLE(pci,scm_pci_tbl);++#define SCM_NUM_MINORS 256 // Total to reserve++staticdev_tscm_dev;+staticstructclass*scm_class;+staticstructmutexminors_idr_lock;+staticstructidrminors_idr;++staticconststructattribute_group*scm_pmem_attribute_groups[]={+&nvdimm_bus_attribute_group,+NULL,+};++staticconststructattribute_group*scm_pmem_region_attribute_groups[]={+&nd_region_attribute_group,+&nd_device_attribute_group,+&nd_mapping_attribute_group,+&nd_numa_attribute_group,+NULL,+};++/**+*scm_ndctl_config_write()-HandleaND_CMD_SET_CONFIG_DATAcommandfromndctl+*@scm_data:theSCMmetadata+*@command:theincomingdatatowrite+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_write(structscm_data*scm_data,+structnd_cmd_set_config_hdr*command)+{+if(command->in_offset+command->in_length>SCM_LABEL_AREA_SIZE)+return-EINVAL;++memcpy_flushcache(scm_data->metadata_addr+command->in_offset,command->in_buf,+command->in_length);++return0;+}++/**+*scm_ndctl_config_read()-HandleaND_CMD_GET_CONFIG_DATAcommandfromndctl+*@scm_data:theSCMmetadata+*@command:thereadrequest+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_read(structscm_data*scm_data,+structnd_cmd_get_config_data_hdr*command)+{+if(command->in_offset+command->in_length>SCM_LABEL_AREA_SIZE)+return-EINVAL;++memcpy_mcsafe(command->out_buf,scm_data->metadata_addr+command->in_offset,+command->in_length);++return0;+}++/**+*scm_ndctl_config_size()-HandleaND_CMD_GET_CONFIG_SIZEcommandfromndctl+*@scm_data:theSCMmetadata+*@command:thereadrequest+*Return:0onsuccess,negativeonfailure+*/+staticintscm_ndctl_config_size(structnd_cmd_get_config_size*command)+{+command->status=0;+command->config_size=SCM_LABEL_AREA_SIZE;+command->max_xfer=PAGE_SIZE;++return0;+}++staticintscm_ndctl(structnvdimm_bus_descriptor*nd_desc,+structnvdimm*nvdimm,+unsignedintcmd,void*buf,unsignedintbuf_len,int*cmd_rc)+{+structscm_data*scm_data=container_of(nd_desc,structscm_data,bus_desc);++switch(cmd){+caseND_CMD_GET_CONFIG_SIZE:+*cmd_rc=scm_ndctl_config_size(buf);+return0;++caseND_CMD_GET_CONFIG_DATA:+*cmd_rc=scm_ndctl_config_read(scm_data,buf);+return0;++caseND_CMD_SET_CONFIG_DATA:+*cmd_rc=scm_ndctl_config_write(scm_data,buf);+return0;++default:+return-ENOTTY;+}+}++staticssize_tserial_show(structdevice*dev,+structdevice_attribute*attr,char*buf)+{+structnvdimm*nvdimm=to_nvdimm(dev);+structscm_data*scm_data=nvdimm_provider_data(nvdimm);+conststructocxl_fn_config*config=ocxl_function_config(scm_data->ocxl_fn);++returnsprintf(buf,"0x%llx\n",config->serial);+}+staticDEVICE_ATTR_RO(serial);++staticstructattribute*scm_dimm_attributes[]={+&dev_attr_serial.attr,+NULL,+};++staticumode_tscm_dimm_attr_visible(structkobject*kobj,+structattribute*a,intn)+{+returna->mode;+}++staticconststructattribute_groupscm_dimm_attribute_group={+.name="ocxl",+.attrs=scm_dimm_attributes,+.is_visible=scm_dimm_attr_visible,+};++staticconststructattribute_group*scm_dimm_attribute_groups[]={+&nvdimm_attribute_group,+&nd_device_attribute_group,+&scm_dimm_attribute_group,+NULL,+};++/**+*scm_reserve_metadata()-Reservespacefornvdimmmetadata+*@scm_data:TheSCMdevicedata+*@lpc_mem:TheresourcerepresentingtheLPCmemoryoftheSCMdevice+*/+staticintscm_reserve_metadata(structscm_data*scm_data,+structresource*lpc_mem)+{+scm_data->metadata_addr=devm_memremap(&scm_data->dev,lpc_mem->start,+SCM_LABEL_AREA_SIZE,MEMREMAP_WB);+if(IS_ERR(scm_data->metadata_addr))+returnPTR_ERR(scm_data->metadata_addr);++return0;+}++/**+*scm_register_lpc_mem()-DiscoverpersistentmemoryonadeviceandregisteritwiththeNVDIMMsubsystem+*@scm_data:TheSCMdevicedata+*Return:0onsuccess+*/+staticintscm_register_lpc_mem(structscm_data*scm_data)+{+structnd_region_descregion_desc;+structnd_mapping_descnd_mapping_desc;+structresource*lpc_mem;+conststructocxl_afu_config*config;+conststructocxl_fn_config*fn_config;+intrc;+unsignedlongnvdimm_cmd_mask=0;+unsignedlongnvdimm_flags=0;+inttarget_node;+charserial[16+1];++// Set up the reserved metadata area+rc=ocxl_afu_map_lpc_mem(scm_data->ocxl_afu);+if(rc<0)+returnrc;++lpc_mem=ocxl_afu_lpc_mem(scm_data->ocxl_afu);+if(lpc_mem==NULL||lpc_mem->start==0)+return-EINVAL;++config=ocxl_afu_config(scm_data->ocxl_afu);+fn_config=ocxl_function_config(scm_data->ocxl_fn);++rc=scm_reserve_metadata(scm_data,lpc_mem);+if(rc)+returnrc;++scm_data->bus_desc.attr_groups=scm_pmem_attribute_groups;+scm_data->bus_desc.provider_name="ocxl-scm";+scm_data->bus_desc.ndctl=scm_ndctl;+scm_data->bus_desc.module=THIS_MODULE;++scm_data->nvdimm_bus=nvdimm_bus_register(&scm_data->dev,+&scm_data->bus_desc);
odd alignment.
+ if (!scm_data->nvdimm_bus)
+ return -EINVAL;
+
+ scm_data->scm_res.start = (u64)lpc_mem->start + SCM_LABEL_AREA_SIZE;
+ scm_data->scm_res.end = (u64)lpc_mem->start + config->lpc_mem_size - 1;
+ scm_data->scm_res.name = "SCM persistent memory";
+
+ set_bit(ND_CMD_GET_CONFIG_SIZE, &nvdimm_cmd_mask);
+ set_bit(ND_CMD_GET_CONFIG_DATA, &nvdimm_cmd_mask);
+ set_bit(ND_CMD_SET_CONFIG_DATA, &nvdimm_cmd_mask);
+
+ set_bit(NDD_ALIASING, &nvdimm_flags);
+
+ snprintf(serial, sizeof(serial), "%llx", fn_config->serial);
+ nd_mapping_desc.nvdimm = nvdimm_create(scm_data->nvdimm_bus, scm_data,
+ scm_dimm_attribute_groups,
+ nvdimm_flags, nvdimm_cmd_mask,
+ 0, NULL);
+ if (!nd_mapping_desc.nvdimm)
+ return -ENOMEM;
+
+ if (nvdimm_bus_check_dimm_count(scm_data->nvdimm_bus, 1))
+ return -EINVAL;
+
+ nd_mapping_desc.start = scm_data->scm_res.start;
+ nd_mapping_desc.size = resource_size(&scm_data->scm_res);
+ nd_mapping_desc.position = 0;
+
+ scm_data->nd_set.cookie1 = fn_config->serial + 1; // allow for empty serial
+ scm_data->nd_set.cookie2 = fn_config->serial + 1;
+
+ target_node = of_node_to_nid(scm_data->pdev->dev.of_node);
+
+ memset(®ion_desc, 0, sizeof(region_desc));
+ region_desc.res = &scm_data->scm_res;
+ region_desc.attr_groups = scm_pmem_region_attribute_groups;
+ region_desc.numa_node = NUMA_NO_NODE;
+ region_desc.target_node = target_node;
+ region_desc.num_mappings = 1;
+ region_desc.mapping = &nd_mapping_desc;
+ region_desc.nd_set = &scm_data->nd_set;
+
+ set_bit(ND_REGION_PAGEMAP, ®ion_desc.flags);
+ /*
+ * NB: libnvdimm copies the data from ndr_desc into it's own
+ * structures so passing a stack pointer is fine.
+ */
+ scm_data->nd_region = nvdimm_pmem_region_create(scm_data->nvdimm_bus,
+ ®ion_desc);
+ if (!scm_data->nd_region)
+ return -EINVAL;
+
+ dev_info(&scm_data->dev,
+ "Onlining %lluMB of persistent memory\n",
+ nd_mapping_desc.size / SZ_1M);
+
+ return 0;
+}
+
+/**
+ * allocate_scm_minor() - Allocate a minor number to use for an SCM device
+ * @scm_data: The SCM device to associate the minor with
+ * Return: the allocated minor number
+ */
+static int allocate_scm_minor(struct scm_data *scm_data)
+{
+ int minor;
+
+ mutex_lock(&minors_idr_lock);
+ minor = idr_alloc(&minors_idr, scm_data, 0, SCM_NUM_MINORS, GFP_KERNEL);
+ mutex_unlock(&minors_idr_lock);
+ return minor;
+}
+
+static void free_scm_minor(struct scm_data *scm_data)
+{
+ mutex_lock(&minors_idr_lock);
+ idr_remove(&minors_idr, MINOR(scm_data->dev.devt));
+ mutex_unlock(&minors_idr_lock);
+}
+
+/**
+ * free_scm() - Free all members of an SCM struct
+ * @scm_data: the SCM metadata to clear
+ */
+static void free_scm(struct scm_data *scm_data)
+{
+ int rc;
+
+ if (scm_data->nvdimm_bus)
+ nvdimm_bus_unregister(scm_data->nvdimm_bus);
+
+ free_scm_minor(scm_data);
+
+ if (scm_data->metadata_addr)
+ devm_memunmap(&scm_data->dev, scm_data->metadata_addr);
+
+ if (scm_data->ocxl_context) {
+ rc = ocxl_context_detach(scm_data->ocxl_context);
+ if (rc == -EBUSY)
+ dev_warn(&scm_data->dev, "Timeout detaching ocxl context\n");
+ else
+ ocxl_context_free(scm_data->ocxl_context);
+
+ }
+
+ if (scm_data->ocxl_afu)
+ ocxl_afu_put(scm_data->ocxl_afu);
+
+ if (scm_data->ocxl_fn)
+ ocxl_function_close(scm_data->ocxl_fn);
+
+ kfree(scm_data);
+}
+
+/**
+ * free_scm_dev - Free an SCM device
+ * @dev: The device struct
+ */
+static void free_scm_dev(struct device *dev)
+{
+ struct scm_data *scm_data = container_of(dev, struct scm_data, dev);
+
+ free_scm(scm_data);
+}
+
+/**
+ * scm_register - Register an SCM device with the kernel
+ * @scm_data: the SCM metadata
+ * Return: 0 on success, negative on failure
+ */
+static int scm_register(struct scm_data *scm_data)
+{
+ int rc;
+ int minor = allocate_scm_minor(scm_data);
+
+ if (minor < 0)
+ return minor;
+
+ scm_data->dev.release = free_scm_dev;
+ rc = dev_set_name(&scm_data->dev, "ocxl-scm%d", minor);
+ if (rc < 0)
+ return rc;
+
+ scm_data->dev.devt = MKDEV(MAJOR(scm_dev), minor);
+ scm_data->dev.class = scm_class;
+ scm_data->dev.parent = &scm_data->pdev->dev;
+
+ rc = device_register(&scm_data->dev);
+ return rc;
return device_register(&scm_data->dev);
Assuming nothing else is added inbetween in later patches...
If it is then ignore this one.
+}
+
+/**
+ * scm_remove() - Free an OpenCAPI Storage Class Memory device
+ * @pdev: the PCI device information struct
+ */
+static void scm_remove(struct pci_dev *pdev)
+{
+ if (PCI_FUNC(pdev->devfn) == 0) {
+ struct scm_function_0 *scm_func_0 = pci_get_drvdata(pdev);
+
+ if (scm_func_0) {
+ ocxl_function_close(scm_func_0->ocxl_fn);
+ scm_func_0->ocxl_fn = NULL;
+ }
+ } else {
+ struct scm_data *scm_data = pci_get_drvdata(pdev);
+
+ if (scm_data)
+ device_unregister(&scm_data->dev);
+ }
+}
+
+/**
+ * scm_probe_function_0 - Set up function 0 for an OpenCAPI Storage Class Memory device
Overly long line + not consistent on () after function name.
IIRC either () or not is fine, but should be consistent in a gven file.
+ * This is important as it enables templates higher than 0 across all other functions,
+ * which in turn enables higher bandwidth accesses
+ * which in turn enables higher bandwidth accesses
Repeated line.
+ * @pdev: the PCI device information struct
+ * Return: 0 on success, negative on failure
+ */
+static int scm_probe_function_0(struct pci_dev *pdev)
+{
+ struct scm_function_0 *scm_func_0 = NULL;
+ struct ocxl_fn *fn;
+
+ scm_func_0 = kzalloc(sizeof(*scm_func_0), GFP_KERNEL);
+ if (!scm_func_0)
+ return -ENOMEM;
+
+ scm_func_0->pdev = pdev;
+ fn = ocxl_function_open(pdev);
+ if (IS_ERR(fn)) {
+ kfree(scm_func_0);
+ dev_err(&pdev->dev, "failed to open OCXL function\n");
+ return PTR_ERR(fn);
+ }
+ scm_func_0->ocxl_fn = fn;
+
+ pci_set_drvdata(pdev, scm_func_0);
+
+ return 0;
+}
+
+/**
+ * scm_probe - Init an OpenCAPI Storage Class Memory device
+ * @pdev: the PCI device information struct
+ * @ent: The entry from scm_pci_tbl
+ * Return: 0 on success, negative on failure
+ */
+static int scm_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct scm_data *scm_data = NULL;
Doesn't seem like scm_data is used anywhere in the rror path..
+ pci_set_drvdata(pdev, NULL);
+ dev_err(&pdev->dev, "failed to open OCXL function\n");
+ goto err;
+ }
+
+ scm_data->ocxl_afu = ocxl_function_fetch_afu(scm_data->ocxl_fn, 0);
+ if (scm_data->ocxl_afu == NULL) {
+ dev_err(&pdev->dev, "Could not get OCXL AFU from function\n");
+ goto err;
The comment below suggests to me that free_scm will only be called if we succeed
in scm_register? If so isn't there more error handling to be done until that
happens?
+ }
+
+ ocxl_afu_get(scm_data->ocxl_afu);
+
+ if (scm_register(scm_data) < 0) {
+ dev_err(&pdev->dev, "Could not register SCM device with the kernel\n");
+ goto err;
+ }
+
+ // Resources allocated below here are cleaned up in the release handler
+
+ if (ocxl_context_alloc(&scm_data->ocxl_context, scm_data->ocxl_afu, NULL)) {
+ dev_err(&pdev->dev, "Could not allocate OCXL context\n");
+ goto err;
+ }
+
+ if (ocxl_context_attach(scm_data->ocxl_context, 0, NULL)) {
+ dev_err(&pdev->dev, "Could not attach ocxl context\n");
+ goto err;
+ }
+
+ if (scm_register_lpc_mem(scm_data)) {
+ dev_err(&pdev->dev, "Could not register OCXL SCM memory with libnvdimm\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ /*
+ * Further cleanup is done in the release handler via free_scm()
+ * This allows us to keep the character device live to handle IOCTLs to
+ * investigate issues if the card has an error
+ */
+
+ dev_err(&pdev->dev,
+ "Error detected, will not register storage class memory\n");
+ return -ENXIO;
Probably better to return more specific errors from the various error paths.
-ENOMEM etc.
On Mon, 2020-02-03 at 13:20 +0000, Jonathan Cameron wrote:
On Tue, 3 Dec 2019 14:46:38 +1100
Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
This driver exposes LPC memory on OpenCAPI SCM cards
as an NVDIMM, allowing the existing nvram infrastructure
to be used.
Namespace metadata is stored on the media itself, so
scm_reserve_metadata() maps 1 section's worth of PMEM storage
at the start to hold this. The rest of the PMEM range is registered
with libnvdimm as an nvdimm. scm_ndctl_config_read/write/size()
provide
callbacks to libnvdimm to access the metadata.
Signed-off-by: Alastair D'Silva <redacted>
Hi Alastair,
A few bits and bobs inline.
Thanks,
Jonathan
+ ®ion_desc);
+ if (!scm_data->nd_region)
+ return -EINVAL;
+
+ dev_info(&scm_data->dev,
+ "Onlining %lluMB of persistent memory\n",
+ nd_mapping_desc.size / SZ_1M);
+
+ return 0;
+}
+
+/**
+ * allocate_scm_minor() - Allocate a minor number to use for an
SCM device
+ * @scm_data: The SCM device to associate the minor with
+ * Return: the allocated minor number
+ */
+static int allocate_scm_minor(struct scm_data *scm_data)
+{
+ int minor;
+
+ mutex_lock(&minors_idr_lock);
+ minor = idr_alloc(&minors_idr, scm_data, 0, SCM_NUM_MINORS,
GFP_KERNEL);
+ mutex_unlock(&minors_idr_lock);
+ return minor;
+}
+
+static void free_scm_minor(struct scm_data *scm_data)
+{
+ mutex_lock(&minors_idr_lock);
+ idr_remove(&minors_idr, MINOR(scm_data->dev.devt));
+ mutex_unlock(&minors_idr_lock);
+}
+
+/**
+ * free_scm() - Free all members of an SCM struct
+ * @scm_data: the SCM metadata to clear
+ */
+static void free_scm(struct scm_data *scm_data)
+{
+ int rc;
+
+ if (scm_data->nvdimm_bus)
+ nvdimm_bus_unregister(scm_data->nvdimm_bus);
+
+ free_scm_minor(scm_data);
+
+ if (scm_data->metadata_addr)
+ devm_memunmap(&scm_data->dev, scm_data->metadata_addr);
+
+ if (scm_data->ocxl_context) {
+ rc = ocxl_context_detach(scm_data->ocxl_context);
+ if (rc == -EBUSY)
+ dev_warn(&scm_data->dev, "Timeout detaching
ocxl context\n");
+ else
+ ocxl_context_free(scm_data->ocxl_context);
+
+ }
+
+ if (scm_data->ocxl_afu)
+ ocxl_afu_put(scm_data->ocxl_afu);
+
+ if (scm_data->ocxl_fn)
+ ocxl_function_close(scm_data->ocxl_fn);
+
+ kfree(scm_data);
+}
+
+/**
+ * free_scm_dev - Free an SCM device
+ * @dev: The device struct
+ */
+static void free_scm_dev(struct device *dev)
+{
+ struct scm_data *scm_data = container_of(dev, struct scm_data,
dev);
+
+ free_scm(scm_data);
+}
+
+/**
+ * scm_register - Register an SCM device with the kernel
+ * @scm_data: the SCM metadata
+ * Return: 0 on success, negative on failure
+ */
+static int scm_register(struct scm_data *scm_data)
+{
+ int rc;
+ int minor = allocate_scm_minor(scm_data);
+
+ if (minor < 0)
+ return minor;
+
+ scm_data->dev.release = free_scm_dev;
+ rc = dev_set_name(&scm_data->dev, "ocxl-scm%d", minor);
+ if (rc < 0)
+ return rc;
+
+ scm_data->dev.devt = MKDEV(MAJOR(scm_dev), minor);
+ scm_data->dev.class = scm_class;
+ scm_data->dev.parent = &scm_data->pdev->dev;
+
+ rc = device_register(&scm_data->dev);
+ return rc;
return device_register(&scm_data->dev);
Assuming nothing else is added inbetween in later patches...
If it is then ignore this one.
Ok
quoted
+}
+
+/**
+ * scm_remove() - Free an OpenCAPI Storage Class Memory device
+ * @pdev: the PCI device information struct
+ */
+static void scm_remove(struct pci_dev *pdev)
+{
+ if (PCI_FUNC(pdev->devfn) == 0) {
+ struct scm_function_0 *scm_func_0 =
pci_get_drvdata(pdev);
+
+ if (scm_func_0) {
+ ocxl_function_close(scm_func_0->ocxl_fn);
+ scm_func_0->ocxl_fn = NULL;
+ }
+ } else {
+ struct scm_data *scm_data = pci_get_drvdata(pdev);
+
+ if (scm_data)
+ device_unregister(&scm_data->dev);
+ }
+}
+
+/**
+ * scm_probe_function_0 - Set up function 0 for an OpenCAPI
Storage Class Memory device
Overly long line + not consistent on () after function name.
IIRC either () or not is fine, but should be consistent in a gven
file.
Ok
quoted
+ * This is important as it enables templates higher than 0 across
all other functions,
+ * which in turn enables higher bandwidth accesses
+ * which in turn enables higher bandwidth accesses
Repeated line.
Ok
quoted
+ * @pdev: the PCI device information struct
+ * Return: 0 on success, negative on failure
+ */
+static int scm_probe_function_0(struct pci_dev *pdev)
+{
+ struct scm_function_0 *scm_func_0 = NULL;
+ struct ocxl_fn *fn;
+
+ scm_func_0 = kzalloc(sizeof(*scm_func_0), GFP_KERNEL);
+ if (!scm_func_0)
+ return -ENOMEM;
+
+ scm_func_0->pdev = pdev;
+ fn = ocxl_function_open(pdev);
+ if (IS_ERR(fn)) {
+ kfree(scm_func_0);
+ dev_err(&pdev->dev, "failed to open OCXL function\n");
+ return PTR_ERR(fn);
+ }
+ scm_func_0->ocxl_fn = fn;
+
+ pci_set_drvdata(pdev, scm_func_0);
+
+ return 0;
+}
+
+/**
+ * scm_probe - Init an OpenCAPI Storage Class Memory device
+ * @pdev: the PCI device information struct
+ * @ent: The entry from scm_pci_tbl
+ * Return: 0 on success, negative on failure
+ */
+static int scm_probe(struct pci_dev *pdev, const struct
pci_device_id *ent)
+{
+ struct scm_data *scm_data = NULL;
Doesn't seem like scm_data is used anywhere in the rror path..
Ok
quoted
+ pci_set_drvdata(pdev, NULL);
+ dev_err(&pdev->dev, "failed to open OCXL function\n");
+ goto err;
+ }
+
+ scm_data->ocxl_afu = ocxl_function_fetch_afu(scm_data->ocxl_fn,
0);
+ if (scm_data->ocxl_afu == NULL) {
+ dev_err(&pdev->dev, "Could not get OCXL AFU from
function\n");
+ goto err;
The comment below suggests to me that free_scm will only be called if
we succeed
in scm_register? If so isn't there more error handling to be done
until that
happens?
I've moved that comment up - once device_register has been called (even
if the call fails), the free handler should always be called via
device_put.
quoted
+ }
+
+ ocxl_afu_get(scm_data->ocxl_afu);
+
+ if (scm_register(scm_data) < 0) {
+ dev_err(&pdev->dev, "Could not register SCM device with
the kernel\n");
+ goto err;
+ }
+
+ // Resources allocated below here are cleaned up in the release
handler
+
+ if (ocxl_context_alloc(&scm_data->ocxl_context, scm_data-
quoted
ocxl_afu, NULL)) {
+ dev_err(&pdev->dev, "Could not allocate OCXL
context\n");
+ goto err;
+ }
+
+ if (ocxl_context_attach(scm_data->ocxl_context, 0, NULL)) {
+ dev_err(&pdev->dev, "Could not attach ocxl context\n");
+ goto err;
+ }
+
+ if (scm_register_lpc_mem(scm_data)) {
+ dev_err(&pdev->dev, "Could not register OCXL SCM memory
with libnvdimm\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ /*
+ * Further cleanup is done in the release handler via
free_scm()
+ * This allows us to keep the character device live to handle
IOCTLs to
+ * investigate issues if the card has an error
+ */
+
+ dev_err(&pdev->dev,
+ "Error detected, will not register storage class
memory\n");
+ return -ENXIO;
Probably better to return more specific errors from the various error
paths.
-ENOMEM etc.
I've updated the code to propogate errors up from the calls.
From: Alastair D'Silva <redacted>
The heartbeat admin command is a simple admin command that exercises
the communication mechanisms within the controller.
This patch issues a heartbeat command to the card during init to ensure
we can communicate with the card's crontroller.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 43 +++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
From: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Date: 2020-02-03 15:11:55
On Tue, 3 Dec 2019 14:46:50 +1100
Alastair D'Silva [off-list ref] wrote:
From: Alastair D'Silva <redacted>
The heartbeat admin command is a simple admin command that exercises
the communication mechanisms within the controller.
This patch issues a heartbeat command to the card during init to ensure
we can communicate with the card's crontroller.
On Mon, 2020-02-03 at 15:11 +0000, Jonathan Cameron wrote:
On Tue, 3 Dec 2019 14:46:50 +1100
Alastair D'Silva [off-list ref] wrote:
quoted
From: Alastair D'Silva <redacted>
The heartbeat admin command is a simple admin command that
exercises
the communication mechanisms within the controller.
This patch issues a heartbeat command to the card during init to
ensure
we can communicate with the card's crontroller.
From: Alastair D'Silva <redacted>
This patch retrieves proprietary formatted SMART data and makes it
available via ndctl. A later contribution will be made to ndctl to
parse this data.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 156 +++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 21 ++++
2 files changed, 177 insertions(+)
@@ -94,6 +94,157 @@ static int scm_ndctl_config_size(struct nd_cmd_get_config_size *command)return0;}+staticintread_smart_attrib(structscm_data*scm_data,u16offset,+structscm_smart_attribs*attribs)+{+u64val;+intrc;+structscm_smart_attrib*attrib;+u8attrib_id;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,offset,OCXL_LITTLE_ENDIAN,+&val);+if(rc)+returnrc;++attrib_id=(val>>56)&0xff;+switch(attrib_id){+caseSCM_SMART_ATTR_POWER_ON_HOURS:+attrib=&attribs->power_on_hours;+break;++caseSCM_SMART_ATTR_TEMPERATURE:+attrib=&attribs->temperature;+break;++caseSCM_SMART_ATTR_LIFE_REMAINING:+attrib=&attribs->life_remaining;+break;++default:+dev_warn(&scm_data->dev,"Unknown smart attrib '%d'",attrib_id);+return-ENOENT;+}++attrib->id=attrib_id;+attrib->attribute_flags=(val>>40)&0xffff;+attrib->current_val=(val>>32)&0xff;+attrib->threshold_val=(val>>24)&0xff;+attrib->worst_val=(val>>16)&0xff;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,offset+0x08,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++attrib->raw_val=val;++return0;+}++/**+*scm_smart_header_parse()-Parsethefirst64bitsoftheSMARTadmincommandresponse+*@scm_data:theSCMmetadata+*@length:out,returnsthenumberofbytesintheresponse(excludingthe64bitheader)+*/+staticintscm_smart_header_parse(structscm_data*scm_data,u32*length)+{+intrc;+u64val;++u16data_identifier;+u32data_length;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++data_identifier=val>>48;+data_length=val&0xFFFFFFFF;++if(data_identifier!=0x534D){+dev_err(&scm_data->dev,+"Bad data identifier for smart data, expected 'SM', got '%-.*s'\n",+2,(char*)&data_identifier);+return-EINVAL;+}++*length=data_length;+return0;+}++staticintscm_smart_update(structscm_data*scm_data)+{+u32length,i;+intrc;++mutex_lock(&scm_data->admin_command.lock);++rc=scm_admin_command_request(scm_data,ADMIN_COMMAND_SMART);+if(rc)+gotoout;++rc=scm_admin_command_execute(scm_data);+if(rc)+gotoout;++rc=scm_admin_command_complete_timeout(scm_data,ADMIN_COMMAND_SMART);+if(rc<0){+dev_err(&scm_data->dev,"SMART timeout\n");+gotoout;+}++rc=scm_admin_response(scm_data);+if(rc<0)+gotoout;+if(rc!=STATUS_SUCCESS){+scm_warn_status(scm_data,"Unexpected status from SMART",rc);+gotoout;+}++rc=scm_smart_header_parse(scm_data,&length);+if(rc)+gotoout;++length/=0x10;// Length now contains the number of attributes++for(i=0;i<length;i++)+read_smart_attrib(scm_data,+scm_data->admin_command.data_offset+0x08+i*0x10,+&scm_data->smart);++rc=scm_admin_response_handled(scm_data);+if(rc)+gotoout;++rc=0;+gotoout;++out:+mutex_unlock(&scm_data->admin_command.lock);+returnrc;+}++staticintscm_ndctl_smart(structscm_data*scm_data,void*buf,+unsignedintbuf_len)+{+intrc;++if(buf_len!=sizeof(scm_data->smart))+return-EINVAL;++rc=scm_smart_update(scm_data);+if(rc)+returnrc;++memcpy(buf,&scm_data->smart,buf_len);++return0;+}++staticintscm_ndctl(structnvdimm_bus_descriptor*nd_desc,structnvdimm*nvdimm,unsignedintcmd,void*buf,unsignedintbuf_len,int*cmd_rc)
@@ -101,6 +252,10 @@ static int scm_ndctl(struct nvdimm_bus_descriptor *nd_desc,structscm_data*scm_data=container_of(nd_desc,structscm_data,bus_desc);switch(cmd){+caseND_CMD_SMART:+*cmd_rc=scm_ndctl_smart(scm_data,buf,buf_len);+return0;+caseND_CMD_GET_CONFIG_SIZE:*cmd_rc=scm_ndctl_config_size(buf);return0;
@@ -300,6 +455,7 @@ static int scm_register_lpc_mem(struct scm_data *scm_data)set_bit(ND_CMD_GET_CONFIG_SIZE,&nvdimm_cmd_mask);set_bit(ND_CMD_GET_CONFIG_DATA,&nvdimm_cmd_mask);set_bit(ND_CMD_SET_CONFIG_DATA,&nvdimm_cmd_mask);+set_bit(ND_CMD_SMART,&nvdimm_cmd_mask);set_bit(NDD_ALIASING,&nvdimm_flags);
On Tue, 2019-12-03 at 14:46 +1100, Alastair D'Silva wrote:
From: Alastair D'Silva <redacted>
This patch retrieves proprietary formatted SMART data and makes it
available via ndctl. A later contribution will be made to ndctl to
parse this data.
Signed-off-by: Alastair D'Silva <redacted>
Dan,
I should ask, is there a defined format that ND_CMD_SMART should be
returning data in, or is it reasonable to have this implementation
dependent?
From: Alastair D'Silva <redacted>
The nvdimm/ocxl driver will be maintained as part of the ppc tree.
I'm also adding myself as an author of the driver & contributor to
the generic ocxl driver.
Signed-off-by: Alastair D'Silva <redacted>
---
MAINTAINERS | 3 +++
1 file changed, 3 insertions(+)
From: Alastair D'Silva <redacted>
The read error log command extracts information from the controller's
internal error log.
This patch exposes this information in 2 ways:
- During probe, if an error occurs & a log is available, print it to the
console
- After probe, make the error log available to userspace via an IOCTL.
Userspace is notified of pending error logs in a later patch
("nvdimm/ocxl: Forward events to userspace")
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 270 +++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 1 +
include/uapi/nvdimm/ocxl-scm.h | 46 +++++
3 files changed, 317 insertions(+)
create mode 100644 include/uapi/nvdimm/ocxl-scm.h
@@ -495,10 +495,220 @@ static int scm_file_release(struct inode *inode, struct file *file)return0;}+/**+*scm_error_log_header_parse()-Parsethefirst64bitsoftheerrorlogcommandresponse+*@scm_data:theSCMmetadata+*@length:out,returnsthenumberofbytesintheresponse(excludingthe64bitheader)+*/+staticintscm_error_log_header_parse(structscm_data*scm_data,u16*length)+{+intrc;+u64val;++u16data_identifier;+u32data_length;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++data_identifier=val>>48;+data_length=val&0xFFFF;++if(data_identifier!=0x454C){+dev_err(&scm_data->dev,+"Bad data identifier for error log data, expected 'EL', got '%2s' (%#x), data_length=%u\n",+(char*)&data_identifier,+(unsignedint)data_identifier,data_length);+return-EINVAL;+}++*length=data_length;+return0;+}++staticintscm_error_log_offset_0x08(structscm_data*scm_data,+u32*log_identifier,u32*program_ref_code)+{+intrc;+u64val;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x08,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+returnrc;++*log_identifier=val>>32;+*program_ref_code=val&0xFFFFFFFF;++return0;+}++staticintscm_read_error_log(structscm_data*scm_data,+structscm_ioctl_error_log*log,boolbuf_is_user)+{+u64val;+u16user_buf_length;+u16buf_length;+u16i;+intrc;++if(log->buf_size%8)+return-EINVAL;++rc=scm_chi(scm_data,&val);+if(rc)+gotoout;++if(!(val&GLOBAL_MMIO_CHI_ELA))+return-EAGAIN;++user_buf_length=log->buf_size;++mutex_lock(&scm_data->admin_command.lock);++rc=scm_admin_command_request(scm_data,ADMIN_COMMAND_ERRLOG);+if(rc)+gotoout;++rc=scm_admin_command_execute(scm_data);+if(rc)+gotoout;++rc=scm_admin_command_complete_timeout(scm_data,ADMIN_COMMAND_ERRLOG);+if(rc<0){+dev_warn(&scm_data->dev,"Read error log timed out\n");+gotoout;+}++rc=scm_admin_response(scm_data);+if(rc<0)+gotoout;+if(rc!=STATUS_SUCCESS){+scm_warn_status(scm_data,"Unexpected status from retrieve error log",rc);+gotoout;+}+++rc=scm_error_log_header_parse(scm_data,&log->buf_size);+if(rc)+gotoout;+// log->buf_size now contains the scm buffer size, not the user size++rc=scm_error_log_offset_0x08(scm_data,&log->log_identifier,+&log->program_reference_code);+if(rc)+gotoout;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x10,+OCXL_LITTLE_ENDIAN,&val);+if(rc)+gotoout;++log->error_log_type=val>>56;+log->action_flags=(log->error_log_type==SCM_ERROR_LOG_TYPE_GENERAL)?+(val>>32)&0xFFFFFF:0;+log->power_on_seconds=val&0xFFFFFFFF;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x18,+OCXL_LITTLE_ENDIAN,&log->timestamp);+if(rc)+gotoout;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x20,+OCXL_HOST_ENDIAN,&log->wwid[0]);+if(rc)+gotoout;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x28,+OCXL_HOST_ENDIAN,&log->wwid[1]);+if(rc)+gotoout;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+0x30,+OCXL_HOST_ENDIAN,(u64*)log->fw_revision);+if(rc)+gotoout;+log->fw_revision[8]='\0';++buf_length=(user_buf_length<log->buf_size)?+user_buf_length:log->buf_size;+for(i=0;i<buf_length+0x48;i+=8){+u64val;++rc=ocxl_global_mmio_read64(scm_data->ocxl_afu,+scm_data->admin_command.data_offset+i,+OCXL_HOST_ENDIAN,&val);+if(rc)+gotoout;++if(buf_is_user){+if(copy_to_user(&log->buf[i],&val,sizeof(u64))){+rc=-EFAULT;+gotoout;+}+}else+log->buf[i]=val;+}++rc=scm_admin_response_handled(scm_data);+if(rc)+gotoout;++out:+mutex_unlock(&scm_data->admin_command.lock);+returnrc;++}++staticintscm_ioctl_error_log(structscm_data*scm_data,+structscm_ioctl_error_log__user*uarg)+{+structscm_ioctl_error_logargs;+intrc;++if(copy_from_user(&args,uarg,sizeof(args)))+return-EFAULT;++rc=scm_read_error_log(scm_data,&args,true);+if(rc)+returnrc;++if(copy_to_user(uarg,&args,sizeof(args)))+return-EFAULT;++return0;+}++staticlongscm_file_ioctl(structfile*file,unsignedintcmd,+unsignedlongargs)+{+structscm_data*scm_data=file->private_data;+intrc=-EINVAL;++switch(cmd){+caseSCM_IOCTL_ERROR_LOG:+rc=scm_ioctl_error_log(scm_data,+(structscm_ioctl_error_log__user*)args);+break;+}+returnrc;+}+staticconststructfile_operationsscm_fops={.owner=THIS_MODULE,.open=scm_file_open,.release=scm_file_release,+.unlocked_ioctl=scm_file_ioctl,+.compat_ioctl=scm_file_ioctl,};/**
On Tue, 2019-12-03 at 14:46 +1100, Alastair D'Silva wrote:
quoted hunk
From: Alastair D'Silva <redacted>
The read error log command extracts information from the controller's
internal error log.
This patch exposes this information in 2 ways:
- During probe, if an error occurs & a log is available, print it to
the
console
- After probe, make the error log available to userspace via an
IOCTL.
Userspace is notified of pending error logs in a later patch
("nvdimm/ocxl: Forward events to userspace")
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 270
+++++++++++++++++++++++++++++
drivers/nvdimm/ocxl/scm_internal.h | 1 +
include/uapi/nvdimm/ocxl-scm.h | 46 +++++
3 files changed, 317 insertions(+)
create mode 100644 include/uapi/nvdimm/ocxl-scm.h
@@ -707,6 +972,11 @@ static int scm_probe(struct pci_dev *pdev, const
struct pci_device_id *ent)
return 0;
err:
+ if (scm_data &&
+ (scm_chi(scm_data, &chi) == 0) &&
+ (chi & GLOBAL_MMIO_CHI_ELA))
+ scm_dump_error_log(scm_data);
+
/*
* Further cleanup is done in the release handler via
free_scm()
* This allows us to keep the character device live to handle
IOCTLs to
diff --git a/drivers/nvdimm/ocxl/scm_internal.h
b/drivers/nvdimm/ocxl/scm_internal.h
index 57491dbee1a4..9bf8fcf30ea6 100644
b/include/uapi/nvdimm/ocxl-scm.h
new file mode 100644
index 000000000000..b34dd1ba06ff
--- /dev/null+++ b/include/uapi/nvdimm/ocxl-scm.h
@@ -0,0 +1,46 @@+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */+/* Copyright 2017 IBM Corp. */+#ifndef _UAPI_OCXL_SCM_H+#define _UAPI_OCXL_SCM_H++#include<linux/types.h>+#include<linux/ioctl.h>++#define SCM_ERROR_LOG_ACTION_RESET (1 << (32-32))+#define SCM_ERROR_LOG_ACTION_CHKFW (1 << (53-32))+#define SCM_ERROR_LOG_ACTION_REPLACE (1 << (54-32))+#define SCM_ERROR_LOG_ACTION_DUMP (1 << (55-32))++#define SCM_ERROR_LOG_TYPE_GENERAL (0x00)+#define SCM_ERROR_LOG_TYPE_PREDICTIVE_FAILURE (0x01)+#define SCM_ERROR_LOG_TYPE_THERMAL_WARNING (0x02)+#define SCM_ERROR_LOG_TYPE_DATA_LOSS (0x03)+#define SCM_ERROR_LOG_TYPE_HEALTH_PERFORMANCE (0x04)++structscm_ioctl_error_log{+__u32log_identifier;// out+__u32program_reference_code;// out+__u32action_flags;//out, recommended course of action+__u32power_on_seconds;// out, Number of seconds the
controller has been on when the error occurred
+ __u64 timestamp; // out, relative time since the current IPL
+ __u64 wwid[2]; // out, the NAA formatted WWID associated with
the controller
+ char fw_revision[8+1]; // out, firmware revision as null
terminated text
+ __u16 buf_size; /* in/out, buffer size provided/required.
+ * If required is greater than provided, the
buffer
+ * will be truncated to the amount provided. If
its
+ * less, then only the required bytes will be
populated.
+ * If it is 0, then there are no more error log
entries.
+ */
+ __u8 error_log_type;
+ __u8 reserved1;
+ __u32 reserved2;
+ __u64 reserved3[2];
+ __u8 *buf; // pointer to output buffer
+};
+
+/* ioctl numbers */
+#define SCM_MAGIC 0x5C
+/* SCM devices */
+#define SCM_IOCTL_ERROR_LOG _IOWR(SCM_MAGIC, 0x01, struct
scm_ioctl_error_log)
+
+#endif /* _UAPI_OCXL_SCM_H */
--
Alastair D'Silva
Open Source Developer
Linux Technology Centre, IBM Australia
mob: 0423 762 819
From: kbuild test robot <hidden> Date: 2019-12-05 19:35:28
Hi Alastair,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on v5.4-rc8]
[also build test ERROR on char-misc/char-misc-testing]
[cannot apply to linux-nvdimm/libnvdimm-for-next linus/master next-20191205]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]
url: https://github.com/0day-ci/linux/commits/Alastair-D-Silva/Add-support-for-OpenCAPI-SCM-devices/20191203-120511
base: af42d3466bdc8f39806b26f593604fdc54140bcb
config: x86_64-randconfig-b001-20191203 (attached as .config)
compiler: gcc-7 (Debian 7.5.0-1) 7.5.0
reproduce:
# save the attached .config to linux build tree
make ARCH=x86_64
If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <redacted>
All errors (new ones prefixed by >>):
In file included from <command-line>:32:0:
quoted
./usr/include/nvdimm/ocxl-scm.h:21:24: error: C++ style comments are not allowed in ISO C90
__u32 log_identifier; // out
^
quoted
./usr/include/nvdimm/ocxl-scm.h:21:24: error: (this will be reported only once per input file)
From: Alastair D'Silva <redacted>
These values have been taken from the device specifications.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm_internal.h | 72 ++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
From: Alastair D'Silva <redacted>
The controller can report a number of statistics that are useful
in evaluating the performance and reliability of the card.
This patch exposes this information via an IOCTL.
Signed-off-by: Alastair D'Silva <redacted>
---
drivers/nvdimm/ocxl/scm.c | 185 +++++++++++++++++++++++++++++++++
include/uapi/nvdimm/ocxl-scm.h | 17 +++
2 files changed, 202 insertions(+)
@@ -84,6 +84,11 @@ struct ocxl_link {intdev;atomic_tirq_available;structspa*spa;+structmutexlpc_mem_lock;+u64lpc_mem_sz;/* Total amount of LPC memory presented on the link */+u64lpc_mem;+intlpc_consumers;+void*platform_data;};staticstructlist_headlinks_list=LIST_HEAD_INIT(links_list);
@@ -396,6 +401,8 @@ static int alloc_link(struct pci_dev *dev, int PE_mask, struct ocxl_link **out_lif(rc)gotoerr_spa;+mutex_init(&link->lpc_mem_lock);+/* platform specific hook */rc=pnv_ocxl_spa_setup(dev,link->spa->spa_mem,PE_mask,&link->platform_data);
@@ -711,3 +718,56 @@ void ocxl_link_free_irq(void *link_handle, int hw_irq)atomic_inc(&link->irq_available);}EXPORT_SYMBOL_GPL(ocxl_link_free_irq);++intocxl_link_add_lpc_mem(void*link_handle,u64offset,u64size)+{+structocxl_link*link=(structocxl_link*)link_handle;++// Check for overflow+if(offset>(offset+size))+return-EINVAL;++mutex_lock(&link->lpc_mem_lock);+link->lpc_mem_sz=max(link->lpc_mem_sz,offset+size);++mutex_unlock(&link->lpc_mem_lock);++return0;+}++u64ocxl_link_lpc_map(void*link_handle,structpci_dev*pdev)+{+structocxl_link*link=(structocxl_link*)link_handle;+u64lpc_mem;++mutex_lock(&link->lpc_mem_lock);+if(link->lpc_mem){+lpc_mem=link->lpc_mem;++link->lpc_consumers++;+mutex_unlock(&link->lpc_mem_lock);+returnlpc_mem;+}++link->lpc_mem=pnv_ocxl_platform_lpc_setup(pdev,link->lpc_mem_sz);+if(link->lpc_mem)+link->lpc_consumers++;+lpc_mem=link->lpc_mem;+mutex_unlock(&link->lpc_mem_lock);++returnlpc_mem;+}++voidocxl_link_lpc_release(void*link_handle,structpci_dev*pdev)+{+structocxl_link*link=(structocxl_link*)link_handle;++mutex_lock(&link->lpc_mem_lock);+WARN_ON(--link->lpc_consumers<0);+if(link->lpc_consumers==0){+pnv_ocxl_platform_lpc_release(pdev);+link->lpc_mem=0;+}++mutex_unlock(&link->lpc_mem_lock);+}
@@ -84,6 +84,11 @@ struct ocxl_link {intdev;atomic_tirq_available;structspa*spa;+structmutexlpc_mem_lock;+u64lpc_mem_sz;/* Total amount of LPC memory presented on the link */+u64lpc_mem;+intlpc_consumers;+void*platform_data;};staticstructlist_headlinks_list=LIST_HEAD_INIT(links_list);
@@ -396,6 +401,8 @@ static int alloc_link(struct pci_dev *dev, int PE_mask, struct ocxl_link **out_lif(rc)gotoerr_spa;+mutex_init(&link->lpc_mem_lock);+/* platform specific hook */rc=pnv_ocxl_spa_setup(dev,link->spa->spa_mem,PE_mask,&link->platform_data);
@@ -711,3 +718,56 @@ void ocxl_link_free_irq(void *link_handle, int hw_irq)atomic_inc(&link->irq_available);}EXPORT_SYMBOL_GPL(ocxl_link_free_irq);++intocxl_link_add_lpc_mem(void*link_handle,u64offset,u64size)+{+structocxl_link*link=(structocxl_link*)link_handle;++// Check for overflow+if(offset>(offset+size))+return-EINVAL;++mutex_lock(&link->lpc_mem_lock);+link->lpc_mem_sz=max(link->lpc_mem_sz,offset+size);++mutex_unlock(&link->lpc_mem_lock);++return0;+}++u64ocxl_link_lpc_map(void*link_handle,structpci_dev*pdev)+{+structocxl_link*link=(structocxl_link*)link_handle;+u64lpc_mem;++mutex_lock(&link->lpc_mem_lock);+if(link->lpc_mem){+lpc_mem=link->lpc_mem;++link->lpc_consumers++;+mutex_unlock(&link->lpc_mem_lock);+returnlpc_mem;+}++link->lpc_mem=pnv_ocxl_platform_lpc_setup(pdev,link->lpc_mem_sz);+if(link->lpc_mem)+link->lpc_consumers++;
So if we fail to setup the lpc access in opal, we don't increment
link->lpc_consumers and we shouldn't call ocxl_link_lpc_release(). Is
that always true? Risk is to trigger the warning in
ocxl_link_lpc_release(). So maybe we should check link->lpc_mem first in
ocxl_link_lpc_release() and exit early if no memory is mapped. It would
probably avoid hitting the WARN on an error path for example.
Fred
Always a good idea to explicitly document what a lock is intended to protect.
quoted hunk
+ u64 lpc_mem_sz; /* Total amount of LPC memory presented on the link */
+ u64 lpc_mem;
+ int lpc_consumers;
+
void *platform_data;
};
static struct list_head links_list = LIST_HEAD_INIT(links_list);
@@ -396,6 +401,8 @@ static int alloc_link(struct pci_dev *dev, int PE_mask, struct ocxl_link **out_l if (rc) goto err_spa;+ mutex_init(&link->lpc_mem_lock);+ /* platform specific hook */ rc = pnv_ocxl_spa_setup(dev, link->spa->spa_mem, PE_mask, &link->platform_data);
If you don't modify this later in the series (I haven't read it all yet :),
it rather feels like it would be more compact and just as readable as
something like...
if (!link->lpc_mem)
link->lpc_mem = pnv_ocxl...
if (link->lpc_mem)
link->lpc_consumers++;
mutex_unlock(&link->lpc_mem_lock);
return link->lpc_mem;
@@ -141,4 +141,37 @@ int ocxl_irq_offset_to_id(struct ocxl_context *ctx, u64 offset);u64ocxl_irq_id_to_offset(structocxl_context*ctx,intirq_id);voidocxl_afu_irq_free_all(structocxl_context*ctx);+/**+*ocxl_link_add_lpc_mem()-IncrementtheamountofmemoryrequiredbyanOpenCAPIlink+*+*@link_handle:TheOpenCAPIlinkhandle+*@offset:Theoffsetofthememorytoadd+*@size:Theamountofmemorytoincrementby+*+*Return0onsuccess,negativeonoverflow+*/+intocxl_link_add_lpc_mem(void*link_handle,u64offset,u64size);++/**+*ocxl_link_lpc_map()-MaptheLPCmemoryforanOpenCAPIdevice+*+*SinceLPCmemorybelongstoalink,thewholeLPCmemoryavailable+*onthelinkbustbemappedinordertomakeitaccessibletoadevice.
must
+ *
+ * @link_handle: The OpenCAPI link handle
+ * @pdev: A device that is on the link
+ */
+u64 ocxl_link_lpc_map(void *link_handle, struct pci_dev *pdev);
+
+/**
+ * ocxl_link_lpc_release() - Release the LPC memory device for an OpenCAPI device
+ *
+ * Offlines LPC memory on an OpenCAPI link for a device. If this is the
+ * last device on the link to release the memory, unmap it from the link.
+ *
+ * @link_handle: The OpenCAPI link handle
+ * @pdev: A device that is on the link
+ */
+void ocxl_link_lpc_release(void *link_handle, struct pci_dev *pdev);
+
#endif /* _OCXL_INTERNAL_H_ */
Always a good idea to explicitly document what a lock is intended to
protect.
Ok
quoted
+ u64 lpc_mem_sz; /* Total amount of LPC memory presented on the
link */
+ u64 lpc_mem;
+ int lpc_consumers;
+
void *platform_data;
};
static struct list_head links_list = LIST_HEAD_INIT(links_list);
@@ -396,6 +401,8 @@ static int alloc_link(struct pci_dev *dev, int
If you don't modify this later in the series (I haven't read it all
yet :),
it rather feels like it would be more compact and just as readable as
something like...
if (!link->lpc_mem)
link->lpc_mem = pnv_ocxl...
if (link->lpc_mem)
link->lpc_consumers++;
mutex_unlock(&link->lpc_mem_lock);
return link->lpc_mem;
@@ -141,4 +141,37 @@ int ocxl_irq_offset_to_id(struct ocxl_context
*ctx, u64 offset);
u64 ocxl_irq_id_to_offset(struct ocxl_context *ctx, int irq_id);
void ocxl_afu_irq_free_all(struct ocxl_context *ctx);
+/**
+ * ocxl_link_add_lpc_mem() - Increment the amount of memory
required by an OpenCAPI link
+ *
+ * @link_handle: The OpenCAPI link handle
+ * @offset: The offset of the memory to add
+ * @size: The amount of memory to increment by
+ *
+ * Return 0 on success, negative on overflow
+ */
+int ocxl_link_add_lpc_mem(void *link_handle, u64 offset, u64
size);
+
+/**
+ * ocxl_link_lpc_map() - Map the LPC memory for an OpenCAPI device
+ *
+ * Since LPC memory belongs to a link, the whole LPC memory
available
+ * on the link bust be mapped in order to make it accessible to a
device.
must
Whoops :)
quoted
+ *
+ * @link_handle: The OpenCAPI link handle
+ * @pdev: A device that is on the link
+ */
+u64 ocxl_link_lpc_map(void *link_handle, struct pci_dev *pdev);
+
+/**
+ * ocxl_link_lpc_release() - Release the LPC memory device for an
OpenCAPI device
+ *
+ * Offlines LPC memory on an OpenCAPI link for a device. If this
is the
+ * last device on the link to release the memory, unmap it from
the link.
+ *
+ * @link_handle: The OpenCAPI link handle
+ * @pdev: A device that is on the link
+ */
+void ocxl_link_lpc_release(void *link_handle, struct pci_dev
*pdev);
+
#endif /* _OCXL_INTERNAL_H_ */
--
Alastair D'Silva
Open Source Developer
Linux Technology Centre, IBM Australia
mob: 0423 762 819