]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
mmc: add more vendor specific and generic data sources (#4006)
authorLeonard Göhrs <leonard@goehrs.eu>
Fri, 3 Jun 2022 13:31:54 +0000 (15:31 +0200)
committerMatthias Runge <mrunge@matthias-runge.de>
Fri, 17 Feb 2023 11:11:48 +0000 (12:11 +0100)
* mmc plugin: integrate into configure.ac

The mmc plugin is not fully integrated in the configure.ac.
Change that.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: Skip mmc paths in /sys that start with a '.' (like "." and "..")

The plugin tries to (and obiously fails to) use "." and "..", that come out of
listdir, as mmc devices.
Filter these two out by skipping hidden files/directories.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: read standard eMMC 5.0 health metrics

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: remove type-name defines

These defines can become confusing, especially when combined with the defines
for attribute names in the sysfs. This will only get worse when more
vendor-specific metrics are supported.
Remove the defines and use the type names directly.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: remove sysfs-attribute defines

These defines are used only once or twice and do not help with readability.
Replace them with just the raw strings.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: port to libudev

While using the sysfs directly works fine for the swissbit and generic eMMC
driver it does not scale well to other vendor-specific interfaces where one has
to open the block device in /dev to perform ioctls.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: add micron eMMC support

While this patch was only tested with a single product (MTFC16GAPALBH) I am
fairly confident that it will generalize to others as well, as micron
themselves ship a single tool[1], which this patch uses as a reference, to read
similar info from all of their eMMCs.

This patch also increases the maximum value of mmc_bad_blocks to infinity,
as it can be any 16 bit integer for micron eMMC but could be even larger for
other vendors.

[1]: https://github.com/arcus-smart-home/arcushubos/blob/master/meta-iris/recipes-core/iris-utils/files/emmcparm_1.0.c

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
* mmc plugin: add sandisk eMMC support

While this patch was only tested with a single product (SDINBDG4-8G), I am
fairly confident that it should generalize to other devices as well,
as the current product portfolio on their website looks very similar to the one
I tested and new devies will likely use a Western Digital manufacturer ID.

Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
Makefile.am
README
configure.ac
src/mmc.c
src/types.db

index f8003cb891cf0cc4e7ace764a02947051d964be0..8dd6a1a82b9e2fb758af80272e9c38f9c51e7093 100644 (file)
@@ -1511,8 +1511,9 @@ endif
 if BUILD_PLUGIN_MMC
 pkglib_LTLIBRARIES += mmc.la
 mmc_la_SOURCES = src/mmc.c
-mmc_la_LDFLAGS = $(PLUGIN_LDFLAGS)
-mmc_la_LIBADD = libignorelist.la
+mmc_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBUDEV_CPPFLAGS)
+mmc_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBUDEV_LDFLAGS)
+mmc_la_LIBADD = libignorelist.la $(BUILD_WITH_LIBUDEV_LIBS)
 endif
 
 if BUILD_PLUGIN_MULTIMETER
diff --git a/README b/README
index bf766efcc316c7c2d52b36d1abeb3d156622eb13..af1b047bfb71cbd744e73f0e948d048e05e38e2a 100644 (file)
--- a/README
+++ b/README
@@ -266,9 +266,10 @@ Features
       Intel Many Integrated Core (MIC) CPUs.
 
     - mmc
-      Reads the percentage of bad blocks on mmc device. The additional values
-      for block erases and power cycles are also read. This Plugin in does only
-      work for the swissbit mmc Cards. MANFID=0x5D OEMID=0x5342
+      Reads the life time estimates reported by eMMC 5.0+ devices and some more
+      detailed health metrics, like bad block and erase counts or power cycles,
+      for micron and sandisk eMMCs and some swissbit mmc Cards (MANFID=0x5D
+      OEMID=0x5342).
 
     - modbus
       Reads values from Modbus/TCP enabled devices. Supports reading values
index 8cfc812e8c35eb62d650d598c1de07a92cda8785..6b90a2a322cf6df8a150da2f99df4b29780459b9 100644 (file)
@@ -6931,6 +6931,10 @@ if test "x$have_termios_h" = "xyes"; then
   plugin_ted="yes"
 fi
 
+if test "x$with_libudev" = "xyes"; then
+  plugin_mmc="yes"
+fi
+
 if test "x$have_thread_info" = "xyes"; then
   plugin_processes="yes"
 fi
@@ -7538,6 +7542,7 @@ AC_MSG_RESULT([    memcachec . . . . . . $enable_memcachec])
 AC_MSG_RESULT([    memcached . . . . . . $enable_memcached])
 AC_MSG_RESULT([    memory  . . . . . . . $enable_memory])
 AC_MSG_RESULT([    mic . . . . . . . . . $enable_mic])
+AC_MSG_RESULT([    mmc . . . . . . . . . $enable_mmc])
 AC_MSG_RESULT([    modbus  . . . . . . . $enable_modbus])
 AC_MSG_RESULT([    mqtt  . . . . . . . . $enable_mqtt])
 AC_MSG_RESULT([    multimeter  . . . . . $enable_multimeter])
index 49a0439b15dea877c8bd84b08e9278871d953121..00e8ee152d415df43c5837bfda4e0be1df2f93af 100644 (file)
--- a/src/mmc.c
+++ b/src/mmc.c
 #error "No applicable input method."
 #endif
 
+#include <libudev.h>
+#include <linux/major.h>
+#include <linux/mmc/ioctl.h>
+#include <sys/ioctl.h>
+
 #define PLUGIN_NAME "mmc"
-#define SYS_PATH "/sys/bus/mmc/devices/"
 
 #define DEVICE_KEY "Device"
 #define IGNORE_KEY "IgnoreSelected"
 
+#define MMC_BLOCK_SIZE 512
+
 static const char *config_keys[] = {
     DEVICE_KEY,
     IGNORE_KEY,
 };
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
-#define MMC_MANUFACTOR "manfid"
-#define MMC_OEM_ID "oemid"
-#define MMC_SSR "ssr"
-
 static ignorelist_t *ignorelist = NULL;
 
 static int mmc_config(const char *key, const char *value) {
@@ -83,83 +85,291 @@ static void mmc_submit(const char *dev_name, const char *type, gauge_t value) {
   plugin_dispatch_values(&vl);
 }
 
-static int mmc_read_dev_attr(const char *dev_name, const char *file_name,
-                             char *buffer, int size) {
-  FILE *fh;
-  char str[sizeof(SYS_PATH) + strlen(dev_name) + sizeof("/") +
-           strlen(file_name) + 1];
-  int length;
-
-  snprintf(str, sizeof(str), SYS_PATH "%s/%s", dev_name, file_name);
-  fh = fopen(str, "r");
+static int mmc_read_manfid(struct udev_device *mmc_dev, int *value) {
+  const char *attr = udev_device_get_sysattr_value(mmc_dev, "manfid");
 
-  if (fh == NULL) {
-    ERROR(PLUGIN_NAME "(%s): Cannot open file [%s]", dev_name, str);
+  if (attr == NULL) {
+    WARNING(PLUGIN_NAME "(%s): Unable to read manufacturer identifier (manfid)",
+            udev_device_get_sysname(mmc_dev));
     return EXIT_FAILURE;
   }
 
-  DEBUG(PLUGIN_NAME "(%s): try to read [%s]", dev_name, str);
-  if (fgets(buffer, size, fh) == NULL) {
-    ERROR(PLUGIN_NAME "(%s): Unable to read file [%s] (%s)", dev_name, str,
-          STRERRNO);
-    fclose(fh);
+  *value = (int)strtol(attr, NULL, 0);
+  return 0;
+}
+
+static int mmc_read_oemid(struct udev_device *mmc_dev, int *value) {
+  const char *attr = udev_device_get_sysattr_value(mmc_dev, "oemid");
+
+  if (attr == NULL) {
+    WARNING(PLUGIN_NAME "(%s): Unable to read original equipment manufacturer "
+                        "identifier (oemid)",
+            udev_device_get_sysname(mmc_dev));
     return EXIT_FAILURE;
   }
-  fclose(fh);
-
-  /* Remove trailing whitespace for sysfs attr read */
-  length = strlen(buffer);
-  DEBUG(PLUGIN_NAME "(%s): Read %d characters [%s]", dev_name, length, str);
-  if (buffer > 0)
-    buffer[length - 1] = '\0';
 
+  *value = (int)strtol(attr, NULL, 0);
   return 0;
 }
+enum mmc_manfid {
+  MANUFACTUR_MICRON = 0x13,
+  MANUFACTUR_SANDISK = 0x45,
+  MANUFACTUR_SWISSBIT = 0x5d,
+};
 
-static int mmc_read_manfid(const char *dev_name, int *value) {
-  char buffer[4096];
+enum mmc_oemid_swissbit {
+  OEMID_SWISSBIT_1 = 21314, // 0x5342
+};
 
-  if (mmc_read_dev_attr(dev_name, MMC_MANUFACTOR, buffer, sizeof(buffer)) ==
-      0) {
-    *value = (int)strtol(buffer, NULL, 0);
-    DEBUG(PLUGIN_NAME "(%s): [%s]=%s (%d)", dev_name, MMC_MANUFACTOR, buffer,
-          *value);
-    return 0;
+static int mmc_read_emmc_generic(struct udev_device *mmc_dev) {
+  const char *dev_name, *attr_life_time, *attr_pre_eol;
+  uint8_t life_time_a, life_time_b, pre_eol;
+  int res = EXIT_FAILURE;
+
+  dev_name = udev_device_get_sysname(mmc_dev);
+  attr_life_time = udev_device_get_sysattr_value(mmc_dev, "life_time");
+  attr_pre_eol = udev_device_get_sysattr_value(mmc_dev, "pre_eol_info");
+
+  // write generic eMMC 5.0 lifetime estimates
+  if (attr_life_time != NULL) {
+    if (sscanf(attr_life_time, "%hhx %hhx", &life_time_a, &life_time_b) == 2) {
+      mmc_submit(dev_name, "mmc_life_time_est_typ_a", (gauge_t)life_time_a);
+      mmc_submit(dev_name, "mmc_life_time_est_typ_b", (gauge_t)life_time_b);
+      res = EXIT_SUCCESS;
+    }
   }
 
-  WARNING(PLUGIN_NAME "(%s): Unable to read manufacturer identifier (manfid)",
-          dev_name);
-  return EXIT_FAILURE;
+  // write generic eMMC 5.0 pre_eol estimate
+  if (attr_pre_eol != NULL) {
+    if (sscanf(attr_pre_eol, "%hhx", &pre_eol) == 1) {
+      mmc_submit(dev_name, "mmc_pre_eol_info", (gauge_t)pre_eol);
+      res = EXIT_SUCCESS;
+    }
+  }
+
+  return res;
 }
 
-static int mmc_read_oemid(const char *dev_name, int *value) {
-  char buffer[4096];
+static int mmc_open_block_dev(const char *dev_name, const char *dev_path) {
+  int block_fd;
+
+  if (dev_path == NULL) {
+    INFO(PLUGIN_NAME "(%s) failed to find block device", dev_name);
+    return -1;
+  }
+
+  block_fd = open(dev_path, O_RDWR);
+  if (block_fd < 0) {
+    INFO(PLUGIN_NAME "(%s) failed to open block device (%s): (%s)", dev_name,
+         dev_path, strerror(errno));
+    return -1;
+  }
+
+  return block_fd;
+}
 
-  if (mmc_read_dev_attr(dev_name, MMC_OEM_ID, buffer, sizeof(buffer)) == 0) {
-    *value = (int)strtol(buffer, NULL, 0);
-    DEBUG(PLUGIN_NAME "(%s): [%s]=%s (%d)", dev_name, MMC_OEM_ID, buffer,
-          *value);
-    return 0;
+// The flags are what emmcparm uses and translate to (include/linux/mmc/core.h):
+// MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_CMD_ADTC |
+// MMC_RSP_SPI_S1
+#define MICRON_CMD56_FLAGS 0x00b5
+#define MICRON_CMD56ARG_BAD_BLOCKS 0x11
+#define MICRON_CMD56ARG_ERASES_SLC 0x23
+#define MICRON_CMD56ARG_ERASES_MLC 0x25
+
+static int mmc_micron_cmd56(int block_fd, uint32_t arg, uint16_t *val1,
+                            uint16_t *val2, uint16_t *val3) {
+  uint16_t cmd_data[MMC_BLOCK_SIZE / sizeof(uint16_t)];
+  struct mmc_ioc_cmd cmd = {
+      .opcode = 56,
+      .arg = arg,
+      .flags = MICRON_CMD56_FLAGS,
+      .blksz = sizeof(cmd_data),
+      .blocks = 1,
+  };
+
+  mmc_ioc_cmd_set_data(cmd, cmd_data);
+
+  if (ioctl(block_fd, MMC_IOC_CMD, &cmd) < 0) {
+    return EXIT_FAILURE;
   }
 
-  WARNING(
-      PLUGIN_NAME
-      "(%s): Unable to read original equipment manufacturer identifier (oemid)",
-      dev_name);
-  return EXIT_FAILURE;
+  *val1 = be16toh(cmd_data[0]);
+  *val2 = be16toh(cmd_data[1]);
+  *val3 = be16toh(cmd_data[2]);
+
+  return EXIT_SUCCESS;
 }
 
-enum mmc_manfid {
-  MANUFACTUR_SWISSBIT = 93, // 0x5d
-};
+static int mmc_read_micron(struct udev_device *mmc_dev,
+                           struct udev_device *block_dev) {
+  uint16_t bb_initial, bb_runtime, bb_remaining, er_slc_min, er_slc_max,
+      er_slc_avg, er_mlc_min, er_mlc_max, er_mlc_avg;
+  const char *dev_name, *dev_path;
+  int block_fd;
+  gauge_t bb_total;
 
-enum mmc_oemid_swissbit {
-  OEMID_SWISSBIT_1 = 21314, // 0x5342
-};
+  dev_name = udev_device_get_sysname(mmc_dev);
+  dev_path = udev_device_get_devnode(block_dev);
+
+  block_fd = mmc_open_block_dev(dev_name, dev_path);
+  if (block_fd < 0) {
+    return EXIT_FAILURE;
+  }
+
+  if (mmc_micron_cmd56(block_fd, MICRON_CMD56ARG_BAD_BLOCKS, &bb_initial,
+                       &bb_runtime, &bb_remaining) != EXIT_SUCCESS) {
+    INFO(PLUGIN_NAME "(%s) failed to send ioctl to %s: %s", dev_name, dev_path,
+         strerror(errno));
+    close(block_fd);
+    return EXIT_FAILURE;
+  }
+
+  if (mmc_micron_cmd56(block_fd, MICRON_CMD56ARG_ERASES_SLC, &er_slc_min,
+                       &er_slc_max, &er_slc_avg) != EXIT_SUCCESS) {
+    INFO(PLUGIN_NAME "(%s) failed to send ioctl to %s: %s", dev_name, dev_path,
+         strerror(errno));
+    close(block_fd);
+    return EXIT_FAILURE;
+  }
+
+  if (mmc_micron_cmd56(block_fd, MICRON_CMD56ARG_ERASES_MLC, &er_mlc_min,
+                       &er_mlc_max, &er_mlc_avg) != EXIT_SUCCESS) {
+    INFO(PLUGIN_NAME "(%s) failed to send ioctl to %s: %s", dev_name, dev_path,
+         strerror(errno));
+    close(block_fd);
+    return EXIT_FAILURE;
+  }
+
+  close(block_fd);
+
+  bb_total = (gauge_t)(bb_initial) + (gauge_t)(bb_runtime);
+
+  mmc_submit(dev_name, "mmc_bad_blocks", bb_total);
+  mmc_submit(dev_name, "mmc_spare_blocks", (gauge_t)(bb_remaining));
+
+  mmc_submit(dev_name, "mmc_erases_slc_min", (gauge_t)(er_slc_min));
+  mmc_submit(dev_name, "mmc_erases_slc_max", (gauge_t)(er_slc_max));
+  mmc_submit(dev_name, "mmc_erases_slc_avg", (gauge_t)(er_slc_avg));
+
+  mmc_submit(dev_name, "mmc_erases_mlc_min", (gauge_t)(er_mlc_min));
+  mmc_submit(dev_name, "mmc_erases_mlc_max", (gauge_t)(er_mlc_max));
+  mmc_submit(dev_name, "mmc_erases_mlc_avg", (gauge_t)(er_mlc_avg));
+
+  return EXIT_SUCCESS;
+}
+
+// Copy what worked for the micron eMMC but allow busy response in report mode
+// enable flags (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_CMD_ADTC |
+// MMC_RSP_BUSY | MMC_RSP_SPI_S1 | MMC_RSP_SPI_BUSY) The _ARG is a magic value
+// from the datasheet
+#define SANDISK_CMD_EN_REPORT_MODE_FLAGS 0x04bd
+#define SANDISK_CMD_EN_REPORT_MODE_OP 62
+#define SANDIKS_CMD_EN_REPORT_MODE_ARG 0x96C9D71C
+
+#define SANDISK_CMD_READ_REPORT_FLAGS 0x00b5
+#define SANDISK_CMD_READ_REPORT_OP 63
+#define SANDISK_CMD_READ_REPORT_ARG 0
+
+// Fields in the Device Report / Advanced Health Status structure
+#define SANDISK_FIELDS_POWER_UPS 25
+#define SANDISK_FIELDS_TEMP_CUR 41
+
+#define SANDISK_FIELDS_BB_INITIAL 6
+#define SANDISK_FIELDS_BB_RUNTIME_MLC 9
+#define SANDISK_FIELDS_BB_RUNTIME_SLC 36
+#define SANDISK_FIELDS_BB_RUNTIME_SYS 7
+
+#define SANDISK_FIELDS_ER_MLC_AVG 2
+#define SANDISK_FIELDS_ER_MLC_MIN 31
+#define SANDISK_FIELDS_ER_MLC_MAX 28
+
+#define SANDISK_FIELDS_ER_SLC_AVG 34
+#define SANDISK_FIELDS_ER_SLC_MIN 33
+#define SANDISK_FIELDS_ER_SLC_MAX 32
+
+#define SANDISK_FIELDS_ER_SYS_AVG 0
+#define SANDISK_FIELDS_ER_SYS_MIN 29
+#define SANDISK_FIELDS_ER_SYS_MAX 26
+
+static int mmc_read_sandisk(struct udev_device *mmc_dev,
+                            struct udev_device *block_dev) {
+  uint32_t cmd_data[MMC_BLOCK_SIZE / sizeof(uint32_t)];
+
+  struct mmc_ioc_cmd cmd_en_report_mode = {
+      .opcode = SANDISK_CMD_EN_REPORT_MODE_OP,
+      .arg = SANDIKS_CMD_EN_REPORT_MODE_ARG,
+      .flags = SANDISK_CMD_EN_REPORT_MODE_FLAGS,
+  };
+  struct mmc_ioc_cmd cmd_read_report = {
+      .opcode = SANDISK_CMD_READ_REPORT_OP,
+      .arg = SANDISK_CMD_READ_REPORT_ARG,
+      .flags = SANDISK_CMD_READ_REPORT_FLAGS,
+      .blksz = sizeof(cmd_data),
+      .blocks = 1,
+  };
+
+  const char *dev_name = udev_device_get_sysname(mmc_dev);
+  const char *dev_path = udev_device_get_devnode(block_dev);
+
+  int block_fd = mmc_open_block_dev(dev_name, dev_path);
+  if (block_fd < 0) {
+    return EXIT_FAILURE;
+  }
+
+  mmc_ioc_cmd_set_data(cmd_read_report, cmd_data);
 
-#define MMC_POWER_CYCLES "mmc_power_cycles"
-#define MMC_BLOCK_ERASES "mmc_block_erases"
-#define MMC_BAD_BLOCKS "mmc_bad_blocks"
+  if (ioctl(block_fd, MMC_IOC_CMD, &cmd_en_report_mode) < 0) {
+    close(block_fd);
+    INFO(PLUGIN_NAME
+         "(%s) failed to send enable report mode MMC ioctl to %s: %s",
+         dev_name, dev_path, strerror(errno));
+    return EXIT_FAILURE;
+  }
+
+  if (ioctl(block_fd, MMC_IOC_CMD, &cmd_read_report) < 0) {
+    close(block_fd);
+    INFO(PLUGIN_NAME "(%s) failed to send read_report MMC ioctl to %s: %s",
+         dev_name, dev_path, strerror(errno));
+    return EXIT_FAILURE;
+  }
+
+  close(block_fd);
+
+  gauge_t bb_total = le32toh(cmd_data[SANDISK_FIELDS_BB_INITIAL]) +
+                     le32toh(cmd_data[SANDISK_FIELDS_BB_RUNTIME_MLC]) +
+                     le32toh(cmd_data[SANDISK_FIELDS_BB_RUNTIME_SLC]) +
+                     le32toh(cmd_data[SANDISK_FIELDS_BB_RUNTIME_SYS]);
+
+  mmc_submit(dev_name, "mmc_bad_blocks", bb_total);
+
+  mmc_submit(dev_name, "mmc_power_cycles",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_POWER_UPS]));
+  mmc_submit(dev_name, "temperature",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_TEMP_CUR]));
+
+  mmc_submit(dev_name, "mmc_erases_mlc_avg",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_MLC_AVG]));
+  mmc_submit(dev_name, "mmc_erases_mlc_max",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_MLC_MAX]));
+  mmc_submit(dev_name, "mmc_erases_mlc_min",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_MLC_MIN]));
+
+  mmc_submit(dev_name, "mmc_erases_slc_avg",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SLC_AVG]));
+  mmc_submit(dev_name, "mmc_erases_slc_max",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SLC_MAX]));
+  mmc_submit(dev_name, "mmc_erases_slc_min",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SLC_MIN]));
+
+  mmc_submit(dev_name, "mmc_erases_sys_avg",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SYS_AVG]));
+  mmc_submit(dev_name, "mmc_erases_sys_max",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SYS_MAX]));
+  mmc_submit(dev_name, "mmc_erases_sys_min",
+             (gauge_t)le32toh(cmd_data[SANDISK_FIELDS_ER_SYS_MIN]));
+
+  return EXIT_SUCCESS;
+}
 
 // Size of string buffer with '\0'
 #define SWISSBIT_LENGTH_SPARE_BLOCKS 3
@@ -170,8 +380,8 @@ enum mmc_oemid_swissbit {
 #define SWISSBIT_SSR_START_BLOCK_ERASES 92
 #define SWISSBIT_SSR_START_POWER_ON 112
 
-static int mmc_read_ssr_swissbit(const char *dev_name) {
-  char buffer[4096];
+static int mmc_read_ssr_swissbit(struct udev_device *mmc_dev) {
+  const char *dev_name, *attr;
   int oemid;
   int value;
   int length;
@@ -179,7 +389,9 @@ static int mmc_read_ssr_swissbit(const char *dev_name) {
   char block_erases[SWISSBIT_LENGTH_BLOCK_ERASES];
   char power_on[SWISSBIT_LENGTH_POWER_ON];
 
-  if (mmc_read_oemid(dev_name, &oemid) != 0) {
+  dev_name = udev_device_get_sysname(mmc_dev);
+
+  if (mmc_read_oemid(mmc_dev, &oemid) != 0) {
     return EXIT_FAILURE;
   }
 
@@ -190,7 +402,9 @@ static int mmc_read_ssr_swissbit(const char *dev_name) {
     return EXIT_FAILURE;
   }
 
-  if (mmc_read_dev_attr(dev_name, MMC_SSR, buffer, sizeof(buffer)) != 0) {
+  attr = udev_device_get_sysattr_value(mmc_dev, "ssr");
+
+  if (attr == NULL) {
     return EXIT_FAILURE;
   }
 
@@ -199,16 +413,17 @@ static int mmc_read_ssr_swissbit(const char *dev_name) {
    * One char represents a half byte (nibble).
    *
    */
-  length = strlen(buffer);
+  length = strlen(attr);
   DEBUG(PLUGIN_NAME ": %d byte read from SSR register", length);
   if (length < 128) {
     INFO(PLUGIN_NAME "(%s): The SSR register is not 128 byte long", dev_name);
     return EXIT_FAILURE;
   }
-  DEBUG(PLUGIN_NAME "(%s): [%s]=%s", dev_name, MMC_SSR, buffer);
 
-  /* write MMC_BAD_BLOCKS */
-  sstrncpy(bad_blocks, &buffer[SWISSBIT_SSR_START_SPARE_BLOCKS],
+  DEBUG(PLUGIN_NAME "(%s): [ssr]=%s", dev_name, attr);
+
+  /* write mmc_bad_blocks */
+  sstrncpy(bad_blocks, &attr[SWISSBIT_SSR_START_SPARE_BLOCKS],
            sizeof(bad_blocks) - 1);
   bad_blocks[sizeof(bad_blocks) - 1] = '\0';
   value = (int)strtol(bad_blocks, NULL, 16);
@@ -216,64 +431,126 @@ static int mmc_read_ssr_swissbit(const char *dev_name) {
   value = abs(value - 100);
   DEBUG(PLUGIN_NAME "(%s): [bad_blocks] str=%s int=%d", dev_name, bad_blocks,
         value);
-  mmc_submit(dev_name, MMC_BAD_BLOCKS, (gauge_t)value);
+  mmc_submit(dev_name, "mmc_bad_blocks", value);
 
-  /* write MMC_BLOCK_ERASES */
-  sstrncpy(block_erases, &buffer[SWISSBIT_SSR_START_BLOCK_ERASES],
+  /* write mmc_block_erases */
+  sstrncpy(block_erases, &attr[SWISSBIT_SSR_START_BLOCK_ERASES],
            sizeof(block_erases) - 1);
   block_erases[sizeof(block_erases) - 1] = '\0';
   value = (int)strtol(block_erases, NULL, 16);
   DEBUG(PLUGIN_NAME "(%s): [block_erases] str=%s int=%d", dev_name,
         block_erases, value);
-  mmc_submit(dev_name, MMC_BLOCK_ERASES, (gauge_t)value);
+  mmc_submit(dev_name, "mmc_block_erases", (gauge_t)value);
 
-  /* write MMC_POWER_CYCLES */
-  sstrncpy(power_on, &buffer[SWISSBIT_SSR_START_POWER_ON],
-           sizeof(power_on) - 1);
+  /* write mmc_power_cycles */
+  sstrncpy(power_on, &attr[SWISSBIT_SSR_START_POWER_ON], sizeof(power_on) - 1);
   power_on[sizeof(power_on) - 1] = '\0';
   value = (int)strtol(power_on, NULL, 16);
   DEBUG(PLUGIN_NAME "(%s): [power_on] str=%s int=%d", dev_name, power_on,
         value);
-  mmc_submit(dev_name, MMC_POWER_CYCLES, (gauge_t)value);
+  mmc_submit(dev_name, "mmc_power_cycles", (gauge_t)value);
 
-  return 0;
+  return EXIT_SUCCESS;
 }
 
 static int mmc_read(void) {
-  DIR *dir;
-  struct dirent *dirent;
+  const char *path, *driver, *dev_name;
+  struct udev *handle_udev;
+  struct udev_enumerate *enumerate;
+  struct udev_list_entry *devices, *dev_list_entry;
+  struct udev_device *block_dev, *mmc_dev;
   int manfid;
+  bool have_stats;
+
+  handle_udev = udev_new();
+  if (!handle_udev) {
+    ERROR(PLUGIN_NAME ": unable to initialize udev for device enumeration");
+    return -1;
+  }
+
+  enumerate = udev_enumerate_new(handle_udev);
+  if (enumerate == NULL) {
+    ERROR(PLUGIN_NAME ": udev_enumerate_new failed");
+    return -1;
+  }
+
+  udev_enumerate_add_match_subsystem(enumerate, "block");
+
+  if (udev_enumerate_scan_devices(enumerate) < 0) {
+    WARNING(PLUGIN_NAME ": udev scan devices failed");
+    return -1;
+  }
 
-  if ((dir = opendir(SYS_PATH)) == NULL) {
-    ERROR(PLUGIN_NAME ": Cannot open directory [%s]", SYS_PATH);
+  devices = udev_enumerate_get_list_entry(enumerate);
+  if (devices == NULL) {
+    WARNING(PLUGIN_NAME ": udev did not return any block devices");
     return -1;
   }
 
-  while ((dirent = readdir(dir)) != NULL) {
-    if (ignorelist_match(ignorelist, dirent->d_name))
+  // Iterate through all block devices in the system
+  udev_list_entry_foreach(dev_list_entry, devices) {
+    path = udev_list_entry_get_name(dev_list_entry);
+    block_dev = udev_device_new_from_syspath(handle_udev, path);
+
+    // Get the parent of the block device.
+    // Note that _get_parent() just gives us its reference to the parent device
+    // and does not increment the reference count, so mmc_dev should not be
+    // _unrefed.
+    mmc_dev = udev_device_get_parent(block_dev);
+    if (!mmc_dev) {
+      udev_device_unref(block_dev);
       continue;
+    }
 
-    if (mmc_read_manfid(dirent->d_name, &manfid) != 0)
+    // Select only block devices that have a mmcblk device as first parent.
+    // This selects e.g. /dev/mmcblk1, but not /dev/mmcblk1p* or
+    // /dev/mmcblk1boot* and especially not /dev/sda, /dev/vda ....
+    driver = udev_device_get_driver(mmc_dev);
+    if (driver == NULL || strcmp(driver, "mmcblk") != 0) {
+      udev_device_unref(block_dev);
       continue;
+    }
 
-    DEBUG(PLUGIN_NAME "(%s): manfid=%d", dirent->d_name, manfid);
-
-    switch (manfid) {
-    case MANUFACTUR_SWISSBIT:
-      mmc_read_ssr_swissbit(dirent->d_name);
-      break;
-    default:
-      INFO(PLUGIN_NAME
-           "(%s): The manufactur id %d is not suppored by this plugin",
-           dirent->d_name, manfid);
-      break;
+    // Check if Device name (Something like "mmc2:0001") matches an entry in the
+    // ignore list
+    dev_name = udev_device_get_sysname(mmc_dev);
+    if (ignorelist_match(ignorelist, dev_name)) {
+      udev_device_unref(block_dev);
+      continue;
     }
+
+    // Read generic health metrics that should be available for all eMMC 5.0+
+    // devices.
+    have_stats = (mmc_read_emmc_generic(mmc_dev) == EXIT_SUCCESS);
+
+    // Read more datailed vendor-specific health info
+    if (mmc_read_manfid(mmc_dev, &manfid) == EXIT_SUCCESS) {
+      switch (manfid) {
+      case MANUFACTUR_MICRON:
+        have_stats |= (mmc_read_micron(mmc_dev, block_dev) == EXIT_FAILURE);
+        break;
+      case MANUFACTUR_SANDISK:
+        have_stats |= (mmc_read_sandisk(mmc_dev, block_dev) == EXIT_FAILURE);
+        break;
+      case MANUFACTUR_SWISSBIT:
+        have_stats |= (mmc_read_ssr_swissbit(mmc_dev) == EXIT_SUCCESS);
+        break;
+      }
+    }
+
+    // Print a warning if no info at all could be collected for a device
+    if (!have_stats) {
+      INFO(PLUGIN_NAME "(%s): Could not collect any info for device", dev_name);
+    }
+
+    udev_device_unref(block_dev);
   }
 
-  closedir(dir);
+  udev_enumerate_unref(enumerate);
+  udev_unref(handle_udev);
 
   return 0;
-}
+} /* int mmc_read */
 
 void module_register(void) {
   plugin_register_config(PLUGIN_NAME, mmc_config, config_keys, config_keys_num);
index 14057e7704aeaab6a7e6a28a588a0a1f03ab973c..888321b47489eb091ea34036de6d8005da41756c 100644 (file)
@@ -167,7 +167,20 @@ memory_lua              value:GAUGE:0:281474976710656
 memory_throttle_count   value:DERIVE:0:U
 mmc_power_cycles        value:GAUGE:0:U
 mmc_block_erases        value:GAUGE:0:U
-mmc_bad_blocks          value:GAUGE:0:255
+mmc_erases_mlc_avg      value:GAUGE:0:U
+mmc_erases_mlc_max      value:GAUGE:0:U
+mmc_erases_mlc_min      value:GAUGE:0:U
+mmc_erases_slc_avg      value:GAUGE:0:U
+mmc_erases_slc_max      value:GAUGE:0:U
+mmc_erases_slc_min      value:GAUGE:0:U
+mmc_erases_sys_avg      value:GAUGE:0:U
+mmc_erases_sys_max      value:GAUGE:0:U
+mmc_erases_sys_min      value:GAUGE:0:U
+mmc_bad_blocks          value:GAUGE:0:U
+mmc_spare_blocks        value:GAUGE:0:U
+mmc_life_time_est_typ_a value:GAUGE:1:11
+mmc_life_time_est_typ_b value:GAUGE:1:11
+mmc_pre_eol_info        value:GAUGE:0:4
 multimeter              value:GAUGE:U:U
 mutex_operations        value:DERIVE:0:U
 mysql_bpool_bytes       value:GAUGE:0:U