432 lines
18 KiB
Diff
432 lines
18 KiB
Diff
|
From 3e687d8072f3ed53ae727ec2cb99ae56dbcdf02b Mon Sep 17 00:00:00 2001
|
||
|
From: Peter Jones <pjones@redhat.com>
|
||
|
Date: Mon, 1 Oct 2018 14:35:01 -0400
|
||
|
Subject: [PATCH 39/39] sas: handle port expanders at all.
|
||
|
|
||
|
Signed-off-by: Peter Jones <pjones@redhat.com>
|
||
|
---
|
||
|
src/linux-ata.c | 3 +-
|
||
|
src/linux-sas.c | 168 ++++++++++++++++++++++++++++++++++++++++++-----
|
||
|
src/linux-scsi.c | 105 +++++++++++++++++++++++++----
|
||
|
src/linux.h | 6 +-
|
||
|
4 files changed, 250 insertions(+), 32 deletions(-)
|
||
|
|
||
|
diff --git a/src/linux-ata.c b/src/linux-ata.c
|
||
|
index 32cb99361e5..43e5f4c5d23 100644
|
||
|
--- a/src/linux-ata.c
|
||
|
+++ b/src/linux-ata.c
|
||
|
@@ -114,7 +114,8 @@ parse_ata(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
|
||
|
pos = parse_scsi_link(host + 1, &scsi_host,
|
||
|
&scsi_bus, &scsi_device,
|
||
|
- &scsi_target, &scsi_lun);
|
||
|
+ &scsi_target, &scsi_lun,
|
||
|
+ NULL, NULL, NULL);
|
||
|
if (pos < 0)
|
||
|
return -1;
|
||
|
|
||
|
diff --git a/src/linux-sas.c b/src/linux-sas.c
|
||
|
index 4d77d39a24d..bb04fe83064 100644
|
||
|
--- a/src/linux-sas.c
|
||
|
+++ b/src/linux-sas.c
|
||
|
@@ -28,6 +28,91 @@
|
||
|
|
||
|
#include "efiboot.h"
|
||
|
|
||
|
+static int
|
||
|
+get_port_expander_sas_address(uint64_t *sas_address, uint32_t scsi_host,
|
||
|
+ uint32_t local_port_id,
|
||
|
+ uint32_t remote_port_id, uint32_t remote_scsi_target)
|
||
|
+{
|
||
|
+ uint8_t *filebuf = NULL;
|
||
|
+ int rc;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * We find sas_address via this insanity:
|
||
|
+ * /sys/class/scsi_host/host2 -> ../../devices/pci0000:74/0000:74:02.0/host2/scsi_host/host2
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/scsi_host/host2/device -> ../../../host2
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/device -> ../../../host2
|
||
|
+ * /sys/devices/host2/port-2:0/expander-2:0/sas_device/expander-2:0/sas_address
|
||
|
+ *
|
||
|
+ * But since host2 is always host2, we can skip most of that and just
|
||
|
+ * go for:
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/sas_device/end_device-2:0:2/sas_address
|
||
|
+ */
|
||
|
+
|
||
|
+#if 0 /* previously thought this was right, but it's the expander's address, not the target's address */
|
||
|
+ /*
|
||
|
+ * /sys/class/scsi_host/host2/device/port-2:0/expander-2:0/sas_device/expander-2:0/sas_address
|
||
|
+ * ... I think. I would have expected that to be port-2:0:0 and I
|
||
|
+ * don't understand why it isn't. (I do now; this is the expander not
|
||
|
+ * the port.)
|
||
|
+ */
|
||
|
+
|
||
|
+ debug("looking for /sys/class/scsi_host/host%d/device/port-%d:%d/expander-%d:%d/sas_device/expander-%d:%d/sas_address",
|
||
|
+ scsi_host, scsi_host, port_id, scsi_host, remote_scsi_target, scsi_host, remote_scsi_target);
|
||
|
+ rc = read_sysfs_file(&filebuf,
|
||
|
+ "class/scsi_host/host%d/device/port-%d:%d/expander-%d:%d/sas_device/expander-%d:%d/sas_address",
|
||
|
+ scsi_host, scsi_host, port_id, scsi_host, remote_scsi_target, scsi_host, remote_scsi_target);
|
||
|
+ if (rc < 0 || filebuf == NULL) {
|
||
|
+ debug("didn't find it.");
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+#else
|
||
|
+ debug("looking for /sys/class/scsi_host/host%d/device/port-%d:%d/expander-%d:%d/port-%d:%d:%d/end_device-%d:%d:%d/sas_device/end_device-%d:%d:%d/sas_address",
|
||
|
+ scsi_host,
|
||
|
+ scsi_host, local_port_id,
|
||
|
+ scsi_host, remote_scsi_target,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id);
|
||
|
+ rc = read_sysfs_file(&filebuf,
|
||
|
+ "class/scsi_host/host%d/device/port-%d:%d/expander-%d:%d/port-%d:%d:%d/end_device-%d:%d:%d/sas_device/end_device-%d:%d:%d/sas_address",
|
||
|
+ scsi_host,
|
||
|
+ scsi_host, local_port_id,
|
||
|
+ scsi_host, remote_scsi_target,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id,
|
||
|
+ scsi_host, remote_scsi_target, remote_port_id);
|
||
|
+ if (rc < 0 || filebuf == NULL) {
|
||
|
+ debug("didn't find it.");
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+#endif
|
||
|
+
|
||
|
+ rc = sscanf((char *)filebuf, "%"PRIx64, sas_address);
|
||
|
+ if (rc != 1)
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+get_local_sas_address(uint64_t *sas_address, struct device *dev)
|
||
|
+{
|
||
|
+ int rc;
|
||
|
+ char *filebuf = NULL;
|
||
|
+
|
||
|
+ rc = read_sysfs_file(&filebuf,
|
||
|
+ "class/block/%s/device/sas_address",
|
||
|
+ dev->disk_name);
|
||
|
+ if (rc < 0 || filebuf == NULL)
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ rc = sscanf((char *)filebuf, "%"PRIx64, sas_address);
|
||
|
+ if (rc != 1)
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
/*
|
||
|
* support for SAS devices
|
||
|
*
|
||
|
@@ -43,6 +128,24 @@
|
||
|
* /sys/class/block/sdc/device/sas_address
|
||
|
*
|
||
|
* I'm not sure at the moment if they're the same or not.
|
||
|
+ *
|
||
|
+ * There are also other devices that look like:
|
||
|
+ *
|
||
|
+ * 8:0 -> ../../devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda
|
||
|
+ * 8:1 -> ../../devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ *
|
||
|
+ * /sys/dev/block/8:0/device -> ../../../2:0:0:0
|
||
|
+ *
|
||
|
+ * This exists:
|
||
|
+ *
|
||
|
+ * /sys/class/scsi_host/host2 -> ../../devices/pci0000:74/0000:74:02.0/host2/scsi_host/host2
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/scsi_host/host2/device -> ../../../host2
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/device -> ../../../host2
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/sas_device/expander-2:0/sas_address
|
||
|
+ *
|
||
|
+ * but the device doesn't actually have a sas_host_address, because it's on a
|
||
|
+ * port expander, and sas_address doesn't directly exist under /sys/class/
|
||
|
+ * anywhere.
|
||
|
*/
|
||
|
static ssize_t
|
||
|
parse_sas(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
@@ -50,16 +153,19 @@ parse_sas(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
struct stat statbuf = { 0, };
|
||
|
int rc;
|
||
|
uint32_t scsi_host, scsi_bus, scsi_device, scsi_target;
|
||
|
+ uint32_t local_port_id = 0, remote_port_id = 0;
|
||
|
+ uint32_t remote_scsi_target = 0;
|
||
|
uint64_t scsi_lun;
|
||
|
ssize_t pos;
|
||
|
- uint8_t *filebuf = NULL;
|
||
|
- uint64_t sas_address;
|
||
|
+ uint64_t sas_address = 0;
|
||
|
|
||
|
debug("entry");
|
||
|
|
||
|
pos = parse_scsi_link(current, &scsi_host,
|
||
|
&scsi_bus, &scsi_device,
|
||
|
- &scsi_target, &scsi_lun);
|
||
|
+ &scsi_target, &scsi_lun,
|
||
|
+ &local_port_id, &remote_port_id,
|
||
|
+ &remote_scsi_target);
|
||
|
/*
|
||
|
* If we can't parse the scsi data, it isn't a sas device, so return 0
|
||
|
* not error.
|
||
|
@@ -71,6 +177,7 @@ parse_sas(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
* Make sure it has the actual /SAS/ bits before we continue
|
||
|
* validating all this junk.
|
||
|
*/
|
||
|
+ debug("looking for /sys/class/scsi_host/host%d/host_sas_address", scsi_host);
|
||
|
rc = sysfs_stat(&statbuf,
|
||
|
"class/scsi_host/host%d/host_sas_address",
|
||
|
scsi_host);
|
||
|
@@ -79,21 +186,48 @@ parse_sas(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
* 0 not error. Later errors mean it is an ata device, but we can't
|
||
|
* parse it right, so they return -1.
|
||
|
*/
|
||
|
- if (rc < 0)
|
||
|
- return 0;
|
||
|
+ if (rc < 0) {
|
||
|
+ debug("didn't find it.");
|
||
|
+ /*
|
||
|
+ * If it's on a port expander, it won't have the
|
||
|
+ * host_sas_address, so we need to check if it's a sas_host
|
||
|
+ * instead.
|
||
|
+ * It may work to just check this to begin with, but I don't
|
||
|
+ * have such a device in front of me right now.
|
||
|
+ */
|
||
|
+ debug("looking for /sys/class/sas_host/host%d", scsi_host);
|
||
|
+ rc = sysfs_stat(&statbuf,
|
||
|
+ "class/sas_host/host%d", scsi_host);
|
||
|
+ if (rc < 0) {
|
||
|
+ debug("didn't find it.");
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+ debug("found it.");
|
||
|
|
||
|
- /*
|
||
|
- * we also need to get the actual sas_address from someplace...
|
||
|
- */
|
||
|
- rc = read_sysfs_file(&filebuf,
|
||
|
- "class/block/%s/device/sas_address",
|
||
|
- dev->disk_name);
|
||
|
- if (rc < 0 || filebuf == NULL)
|
||
|
- return -1;
|
||
|
-
|
||
|
- rc = sscanf((char *)filebuf, "%"PRIx64, &sas_address);
|
||
|
- if (rc != 1)
|
||
|
- return -1;
|
||
|
+ /*
|
||
|
+ * So it *is* a sas_host, and we have to fish the sas_address
|
||
|
+ * from the remote port
|
||
|
+ */
|
||
|
+ rc = get_port_expander_sas_address(&sas_address, scsi_host,
|
||
|
+ local_port_id,
|
||
|
+ remote_port_id,
|
||
|
+ remote_scsi_target);
|
||
|
+ if (rc < 0) {
|
||
|
+ debug("Couldn't find port expander sas address");
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+ } else {
|
||
|
+ /*
|
||
|
+ * we also need to get the actual sas_address from someplace...
|
||
|
+ */
|
||
|
+ debug("found it.");
|
||
|
+ rc = get_local_sas_address(&sas_address, dev);
|
||
|
+ if (rc < 0) {
|
||
|
+ debug("Couldn't find sas address");
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ debug("sas address is 0x%"PRIx64, sas_address);
|
||
|
|
||
|
dev->sas_info.sas_address = sas_address;
|
||
|
|
||
|
diff --git a/src/linux-scsi.c b/src/linux-scsi.c
|
||
|
index 2e4f710badf..a5e81cf9cb6 100644
|
||
|
--- a/src/linux-scsi.c
|
||
|
+++ b/src/linux-scsi.c
|
||
|
@@ -38,7 +38,9 @@
|
||
|
ssize_t HIDDEN
|
||
|
parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
uint32_t *scsi_bus, uint32_t *scsi_device,
|
||
|
- uint32_t *scsi_target, uint64_t *scsi_lun)
|
||
|
+ uint32_t *scsi_target, uint64_t *scsi_lun,
|
||
|
+ uint32_t *local_port_id, uint32_t *remote_port_id,
|
||
|
+ uint32_t *remote_target_id)
|
||
|
{
|
||
|
int rc;
|
||
|
int sz = 0;
|
||
|
@@ -70,11 +72,32 @@ parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
* /sys/block/sdc/device looks like:
|
||
|
* device-> ../../../4:2:0:0
|
||
|
*
|
||
|
+ * OR
|
||
|
+ *
|
||
|
+ * 8:0 -> ../../devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda
|
||
|
+ * 8:1 -> ../../devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ *
|
||
|
+ * /sys/block/sda/device looks like:
|
||
|
+ * device -> ../../../2:0:0:0 *
|
||
|
+ *
|
||
|
+ * sas_address exists, but it's hard to find:
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/sas_device/expander-2:0/sas_address
|
||
|
+ * but sas_host_address is nowhere to be found, and sas_address
|
||
|
+ * doesn't directly exist under /sys/class/ anywhere. So you actually
|
||
|
+ * have to go to
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/sas_device/expander-2:0/sas_address
|
||
|
+ * and chop that off to
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/
|
||
|
+ * and then add a bunch of port and end device crap to it to get:
|
||
|
+ * /sys/devices/pci0000:74/0000:74:02.0/host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/sas_device/end_device-2:0:2/sas_address
|
||
|
+
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* So we start when current is:
|
||
|
* host4/port-4:0/end_device-4:0/target4:0:0/4:0:0:0/block/sdc/sdc1
|
||
|
+ * or
|
||
|
+ * host2/port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
*/
|
||
|
uint32_t tosser0, tosser1, tosser2;
|
||
|
|
||
|
@@ -91,6 +114,14 @@ parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
sz += pos0;
|
||
|
pos0 = 0;
|
||
|
|
||
|
+ /*
|
||
|
+ * We might have this next:
|
||
|
+ * port-2:0/expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ * or:
|
||
|
+ * port-2:0/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ * or maybe (not sure):
|
||
|
+ * port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ */
|
||
|
debug("searching for port-4:0 or port-4:0:0");
|
||
|
rc = sscanf(current+sz, "port-%d:%d%n:%d%n", &tosser0,
|
||
|
&tosser1, &pos0, &tosser2, &pos1);
|
||
|
@@ -100,6 +131,52 @@ parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
if (rc == 2 || rc == 3) {
|
||
|
sz += pos0;
|
||
|
pos0 = 0;
|
||
|
+ if (local_port_id && rc == 2)
|
||
|
+ *local_port_id = tosser1;
|
||
|
+ if (remote_port_id && rc == 3)
|
||
|
+ *remote_port_id = tosser2;
|
||
|
+
|
||
|
+ if (current[sz] == '/')
|
||
|
+ sz += 1;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * We might have this next:
|
||
|
+ * expander-2:0/port-2:0:2/end_device-2:0:2/target2:0:0/2:0:0:0/block/sda/sda1
|
||
|
+ * ^ port id
|
||
|
+ * ^ scsi target id
|
||
|
+ * ^ host number
|
||
|
+ * ^ host number
|
||
|
+ * We don't actually care about either number in expander-.../,
|
||
|
+ * because they're replicated in all the other places. We just need
|
||
|
+ * to get past it.
|
||
|
+ */
|
||
|
+ debug("searching for expander-4:0/");
|
||
|
+ rc = sscanf(current+sz, "expander-%d:%d/%n", &tosser0, &tosser1, &pos0);
|
||
|
+ debug("current:\"%s\" rc:%d pos0:%d\n", current+sz, rc, pos0);
|
||
|
+ arrow(LOG_DEBUG, spaces, 9, pos0, rc, 2);
|
||
|
+ if (rc == 2) {
|
||
|
+ if (!remote_target_id) {
|
||
|
+ efi_error("Device is PHY is a remote target, but remote_target_id is NULL");
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+ *remote_target_id = tosser1;
|
||
|
+ sz += pos0;
|
||
|
+ pos0 = 0;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * if we have that, we should have a 3-part port next
|
||
|
+ */
|
||
|
+ debug("searching for port-2:0:2/");
|
||
|
+ rc = sscanf(current+sz, "port-%d:%d:%d/%n", &tosser0, &tosser1, &tosser2, &pos0);
|
||
|
+ debug("current:\"%s\" rc:%d pos0:%d\n", current+sz, rc, pos0);
|
||
|
+ arrow(LOG_DEBUG, spaces, 9, pos0, rc, 3);
|
||
|
+ if (rc != 3) {
|
||
|
+ efi_error("Couldn't parse port expander port string");
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+ sz += pos0;
|
||
|
+ }
|
||
|
+ pos0 = 0;
|
||
|
|
||
|
/* next:
|
||
|
* /end_device-4:0
|
||
|
@@ -107,22 +184,24 @@ parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
* awesomely these are the exact same fields that go into port-blah,
|
||
|
* but we don't care for now about any of them anyway.
|
||
|
*/
|
||
|
- debug("searching for /end_device-4:0/ or /end_device-4:0:0/");
|
||
|
- rc = sscanf(current + sz, "/end_device-%d:%d%n", &tosser0, &tosser1, &pos0);
|
||
|
+ debug("searching for end_device-4:0/ or end_device-4:0:0/");
|
||
|
+ rc = sscanf(current + sz, "end_device-%d:%d%n", &tosser0, &tosser1, &pos0);
|
||
|
debug("current:\"%s\" rc:%d pos0:%d\n", current+sz, rc, pos0);
|
||
|
- arrow(LOG_DEBUG, spaces, 9, pos0, rc, 2);
|
||
|
if (rc != 2)
|
||
|
return -1;
|
||
|
- sz += pos0;
|
||
|
- pos0 = 0;
|
||
|
|
||
|
- rc = sscanf(current + sz, ":%d%n", &tosser0, &pos0);
|
||
|
- debug("current:\"%s\" rc:%d pos0:%d\n", current+sz, rc, pos0);
|
||
|
- arrow(LOG_DEBUG, spaces, 9, pos0, rc, 2);
|
||
|
+ pos1 = 0;
|
||
|
+ rc = sscanf(current + sz + pos0, ":%d%n", &tosser2, &pos1);
|
||
|
+ arrow(LOG_DEBUG, spaces, 9, pos0, rc + 2, 2);
|
||
|
+ arrow(LOG_DEBUG, spaces, 9, pos0 + pos1, rc + 2, 3);
|
||
|
if (rc != 0 && rc != 1)
|
||
|
return -1;
|
||
|
- sz += pos0;
|
||
|
- pos0 = 0;
|
||
|
+ if (remote_port_id && rc == 1)
|
||
|
+ *remote_port_id = tosser2;
|
||
|
+ if (local_port_id && rc == 0)
|
||
|
+ *local_port_id = tosser1;
|
||
|
+ sz += pos0 + pos1;
|
||
|
+ pos0 = pos1 = 0;
|
||
|
|
||
|
if (current[sz] == '/')
|
||
|
sz += 1;
|
||
|
@@ -156,6 +235,7 @@ parse_scsi_link(const char *current, uint32_t *scsi_host,
|
||
|
return -1;
|
||
|
sz += pos0;
|
||
|
|
||
|
+ debug("returning %d", sz);
|
||
|
return sz;
|
||
|
}
|
||
|
|
||
|
@@ -191,7 +271,8 @@ parse_scsi(struct device *dev, const char *current, const char *root UNUSED)
|
||
|
|
||
|
sz = parse_scsi_link(current, &scsi_host,
|
||
|
&scsi_bus, &scsi_device,
|
||
|
- &scsi_target, &scsi_lun);
|
||
|
+ &scsi_target, &scsi_lun,
|
||
|
+ NULL, NULL, NULL);
|
||
|
if (sz < 0)
|
||
|
return 0;
|
||
|
|
||
|
diff --git a/src/linux.h b/src/linux.h
|
||
|
index 7c7ea91e771..43a9b7899f5 100644
|
||
|
--- a/src/linux.h
|
||
|
+++ b/src/linux.h
|
||
|
@@ -267,8 +267,10 @@ struct dev_probe {
|
||
|
};
|
||
|
|
||
|
extern ssize_t parse_scsi_link(const char *current, uint32_t *host,
|
||
|
- uint32_t *bus, uint32_t *device,
|
||
|
- uint32_t *target, uint64_t *lun);
|
||
|
+ uint32_t *bus, uint32_t *device,
|
||
|
+ uint32_t *target, uint64_t *lun,
|
||
|
+ uint32_t *local_port_id, uint32_t *remote_port_id,
|
||
|
+ uint32_t *remote_target_id);
|
||
|
|
||
|
/* device support implementations */
|
||
|
extern struct dev_probe pmem_parser;
|
||
|
--
|
||
|
2.17.1
|
||
|
|