* [PATCH v2 0/5] mmc-utils: improve lsmmc usability
@ 2026-05-15 21:37 Torstein Eide
2026-05-15 21:37 ` [PATCH v2 1/5] mmc-utils: lsmmc: Refactor CID parsing into shared structs Torstein Eide
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
This series improves lsmmc in four areas: external manufacturer ID files,
accepting /dev and /sys/block paths for register reads, an mmc list command,
and bash completion for mmc-utils.
A cleanup patch is prepended to refactor CID parsing into shared structs and
helpers used by all three print functions.
Changes since v1 (review by Avri Altman):
- Patch 1 (new): refactor CID parsing into struct sd_cid/mmc_cid and
parse_sd_cid()/parse_mmc_cid() helpers; used by print_sd_cid,
print_mmc_cid, and print_list_entry
- Patch 2: get_manufacturer: fix static buffer lifetime, return strdup()
- Patch 2: get_manufacturer: simplify fopen fallback, warn once if ids
files missing
- Patch 2: replace read_file() with read_file_at(dir, name), remove all
chdir() calls
- Patch 2: struct config: remove ids_dir field
- Patch 2: print_sd_cid, print_mmc_cid: free() the manufacturer string
- Patch 3: resolve_dev_path: use stack buffer, strip partition suffix,
validate device type
- Patch 4: month_name: switch to 0-indexed 16-entry array, drop "???" sentinel
- Patch 4: do_list: move struct config outside loop
- Patch 4: do_list: remove redundant access() check
- Patch 5: bash completion: accept more than 9 devices (/dev/mmcblk[0-9]
replaced with compgen -G + grep regex)
Torstein Eide (5):
mmc-utils: lsmmc: Refactor CID parsing into shared structs
mmc-utils: lsmmc: Use external .ids files for manufacturer lookup
mmc-utils: lsmmc: Accept /dev and /sys/block paths for register reads
mmc-utils: lsmmc: Add mmc list command
mmc-utils: Add bash completion
Makefile | 11 +-
completion/mmc | 62 +++++
docs/HOWTO.rst | 77 +++++-
lsmmc.c | 594 ++++++++++++++++++++++++++-------------------
mmc.c | 6 +-
mmc_cmds.h | 1 +
multimediacard.ids | 16 ++
sdcard.ids | 23 ++
8 files changed, 531 insertions(+), 259 deletions(-)
create mode 100644 completion/mmc
create mode 100644 multimediacard.ids
create mode 100644 sdcard.ids
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/5] mmc-utils: lsmmc: Refactor CID parsing into shared structs
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
@ 2026-05-15 21:37 ` Torstein Eide
2026-05-15 21:37 ` [PATCH v2 2/5] mmc-utils: lsmmc: Use external .ids files for manufacturer lookup Torstein Eide
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
Add struct sd_cid and struct mmc_cid with parse_sd_cid() and
parse_mmc_cid() helpers. Use them in print_sd_cid(), print_mmc_cid()
and print_list_entry() to eliminate duplicated parse_bin() format
strings and local variable lists.
Also fix print_list_entry() using const char * for the strdup'd
manufacturer string without a matching free().
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
---
lsmmc.c | 250 +++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 184 insertions(+), 66 deletions(-)
diff --git a/lsmmc.c b/lsmmc.c
index 799e1ea..8155a5c 100644
--- a/lsmmc.c
+++ b/lsmmc.c
@@ -509,62 +509,89 @@ static void parse_bin(char *hexstr, char *fmt, ...)
free(origstr);
}
-/* MMC/SD information parsing functions */
-static void print_sd_cid(struct config *config, char *cid)
-{
- static const char *months[] = {
- "jan", "feb", "mar", "apr", "may", "jun",
- "jul", "aug", "sep", "oct", "nov", "dec",
- "invalid0", "invalid1", "invalid2", "invalid3",
- };
+struct sd_cid {
unsigned int mid;
char oid[3];
char pnm[6];
unsigned int prv_major;
unsigned int prv_minor;
unsigned int psn;
+ unsigned int mdt_year;
unsigned int mdt_month;
+ unsigned int crc;
+};
+
+struct mmc_cid {
+ unsigned int mid;
+ unsigned int cbx;
+ unsigned int oid;
+ char pnm[7];
+ unsigned int prv_major;
+ unsigned int prv_minor;
+ unsigned int psn;
unsigned int mdt_year;
+ unsigned int mdt_month;
unsigned int crc;
- char *manufacturer = NULL;
+};
- parse_bin(cid, "8u16a40a4u4u32u4r8u4u7u1r",
- &mid, &oid[0], &pnm[0], &prv_major, &prv_minor, &psn,
- &mdt_year, &mdt_month, &crc);
+static void parse_sd_cid(char *raw, struct sd_cid *c)
+{
+ parse_bin(raw, "8u16a40a4u4u32u4r8u4u7u1r",
+ &c->mid, &c->oid[0], &c->pnm[0], &c->prv_major, &c->prv_minor,
+ &c->psn, &c->mdt_year, &c->mdt_month, &c->crc);
+ c->oid[2] = '\0';
+ c->pnm[5] = '\0';
+}
- oid[2] = '\0';
- pnm[5] = '\0';
+static void parse_mmc_cid(char *raw, struct mmc_cid *c)
+{
+ parse_bin(raw, "8u6r2u8u48a4u4u32u4u4u7u1r",
+ &c->mid, &c->cbx, &c->oid, &c->pnm[0], &c->prv_major,
+ &c->prv_minor, &c->psn, &c->mdt_year, &c->mdt_month, &c->crc);
+ c->pnm[6] = '\0';
+}
+
+/* MMC/SD information parsing functions */
+static void print_sd_cid(struct config *config, char *cid)
+{
+ static const char *months[] = {
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec",
+ "invalid0", "invalid1", "invalid2", "invalid3",
+ };
+ struct sd_cid c;
+ char *manufacturer;
- manufacturer = get_manufacturer(config, mid);
+ parse_sd_cid(cid, &c);
+ manufacturer = get_manufacturer(config, c.mid);
if (config->verbose) {
printf("======SD/CID======\n");
- printf("\tMID: 0x%02x (", mid);
+ printf("\tMID: 0x%02x (", c.mid);
if (manufacturer)
printf("%s)\n", manufacturer);
else
printf("Unlisted)\n");
- printf("\tOID: %s\n", oid);
- printf("\tPNM: %s\n", pnm);
- printf("\tPRV: 0x%01x%01x ", prv_major, prv_minor);
- printf("(%u.%u)\n", prv_major, prv_minor);
- printf("\tPSN: 0x%08x\n", psn);
- printf("\tMDT: 0x%02x%01x %u %s\n", mdt_year, mdt_month,
- 2000 + mdt_year, months[mdt_month]);
- printf("\tCRC: 0x%02x\n", crc);
+ printf("\tOID: %s\n", c.oid);
+ printf("\tPNM: %s\n", c.pnm);
+ printf("\tPRV: 0x%01x%01x ", c.prv_major, c.prv_minor);
+ printf("(%u.%u)\n", c.prv_major, c.prv_minor);
+ printf("\tPSN: 0x%08x\n", c.psn);
+ printf("\tMDT: 0x%02x%01x %u %s\n", c.mdt_year, c.mdt_month,
+ 2000 + c.mdt_year, months[c.mdt_month]);
+ printf("\tCRC: 0x%02x\n", c.crc);
} else {
if (manufacturer)
- printf("manufacturer: '%s' '%s'\n",
- manufacturer, oid);
+ printf("manufacturer: '%s' '%s'\n", manufacturer, c.oid);
else
- printf("manufacturer: 'Unlisted' '%s'\n", oid);
+ printf("manufacturer: 'Unlisted' '%s'\n", c.oid);
- printf("product: '%s' %u.%u\n", pnm, prv_major, prv_minor);
- printf("serial: 0x%08x\n", psn);
- printf("manufacturing date: %u %s\n", 2000 + mdt_year,
- months[mdt_month]);
+ printf("product: '%s' %u.%u\n", c.pnm, c.prv_major, c.prv_minor);
+ printf("serial: 0x%08x\n", c.psn);
+ printf("manufacturing date: %u %s\n", 2000 + c.mdt_year,
+ months[c.mdt_month]);
}
}
@@ -575,37 +602,23 @@ static void print_mmc_cid(struct config *config, char *cid)
"jul", "aug", "sep", "oct", "nov", "dec",
"invalid0", "invalid1", "invalid2", "invalid3",
};
- unsigned int mid;
- unsigned int cbx;
- unsigned int oid;
- char pnm[7];
- unsigned int prv_major;
- unsigned int prv_minor;
- unsigned int psn;
- unsigned int mdt_month;
- unsigned int mdt_year;
- unsigned int crc;
- char *manufacturer = NULL;
-
- parse_bin(cid, "8u6r2u8u48a4u4u32u4u4u7u1r",
- &mid, &cbx, &oid, &pnm[0], &prv_major, &prv_minor, &psn,
- &mdt_year, &mdt_month, &crc);
-
- pnm[6] = '\0';
+ struct mmc_cid c;
+ char *manufacturer;
- manufacturer = get_manufacturer(config, mid);
+ parse_mmc_cid(cid, &c);
+ manufacturer = get_manufacturer(config, c.mid);
if (config->verbose) {
printf("======MMC/CID======\n");
- printf("\tMID: 0x%02x (", mid);
+ printf("\tMID: 0x%02x (", c.mid);
if (manufacturer)
printf("%s)\n", manufacturer);
else
printf("Unlisted)\n");
- printf("\tCBX: 0x%01x (", cbx);
- switch (cbx) {
+ printf("\tCBX: 0x%01x (", c.cbx);
+ switch (c.cbx) {
case 0:
printf("card)\n");
break;
@@ -620,25 +633,26 @@ static void print_mmc_cid(struct config *config, char *cid)
break;
}
- printf("\tOID: 0x%01x\n", oid);
- printf("\tPNM: %s\n", pnm);
- printf("\tPRV: 0x%01x%01x ", prv_major, prv_minor);
- printf("(%u.%u)\n", prv_major, prv_minor);
- printf("\tPSN: 0x%08x\n", psn);
- printf("\tMDT: 0x%01x%01x %u %s\n", mdt_month, mdt_year,
- 1997 + mdt_year, months[mdt_month]);
- printf("\tCRC: 0x%02x\n", crc);
+ printf("\tOID: 0x%01x\n", c.oid);
+ printf("\tPNM: %s\n", c.pnm);
+ printf("\tPRV: 0x%01x%01x ", c.prv_major, c.prv_minor);
+ printf("(%u.%u)\n", c.prv_major, c.prv_minor);
+ printf("\tPSN: 0x%08x\n", c.psn);
+ printf("\tMDT: 0x%01x%01x %u %s\n", c.mdt_month, c.mdt_year,
+ 1997 + c.mdt_year, months[c.mdt_month]);
+ printf("\tCRC: 0x%02x\n", c.crc);
} else {
if (manufacturer)
printf("manufacturer: 0x%02x (%s) oid: 0x%01x\n",
- mid, manufacturer, oid);
+ c.mid, manufacturer, c.oid);
else
- printf("manufacturer: 0x%02x (Unlisted) oid: 0x%01x\n", mid, oid);
+ printf("manufacturer: 0x%02x (Unlisted) oid: 0x%01x\n",
+ c.mid, c.oid);
- printf("product: '%s' %u.%u\n", pnm, prv_major, prv_minor);
- printf("serial: 0x%08x\n", psn);
- printf("manufacturing date: %u %s\n", 1997 + mdt_year,
- months[mdt_month]);
+ printf("product: '%s' %u.%u\n", c.pnm, c.prv_major, c.prv_minor);
+ printf("serial: 0x%08x\n", c.psn);
+ printf("manufacturing date: %u %s\n", 1997 + c.mdt_year,
+ months[c.mdt_month]);
}
}
@@ -2270,6 +2284,110 @@ static int do_read_reg(int argc, char **argv, enum REG_TYPE reg)
return ret;
}
+static const char *month_name(unsigned int month)
+{
+ static const char *months[] = {
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec",
+ "invalid0", "invalid1", "invalid2", "invalid3",
+ };
+
+ if (month >= ARRAY_SIZE(months))
+ return "invalid";
+
+ return months[month];
+}
+
+static void print_list_entry(struct config *cfg, const char *devname,
+ const char *blkdev, char *cid)
+{
+ char *mfr;
+ char devnode[32];
+
+ snprintf(devnode, sizeof(devnode), "/dev/%s", blkdev ? blkdev : "?");
+
+ if (cfg->bus == SD) {
+ struct sd_cid c;
+
+ parse_sd_cid(cid, &c);
+ mfr = get_manufacturer(cfg, c.mid);
+ printf("%-14s %-14s SD %-20s %-10s %u.%u 0x%08x %u-%s\n",
+ devname, devnode, mfr ? mfr : "Unlisted", c.pnm,
+ c.prv_major, c.prv_minor, c.psn, 2000 + c.mdt_year,
+ month_name(c.mdt_month));
+ } else {
+ struct mmc_cid c;
+
+ parse_mmc_cid(cid, &c);
+ mfr = get_manufacturer(cfg, c.mid);
+ printf("%-14s %-14s MMC %-20s %-10s %u.%u 0x%08x %u-%s\n",
+ devname, devnode, mfr ? mfr : "Unlisted", c.pnm,
+ c.prv_major, c.prv_minor, c.psn, 1997 + c.mdt_year,
+ month_name(c.mdt_month));
+ }
+
+ free(mfr);
+}
+
+int do_list(int nargs, char **argv)
+{
+ const char *bus_path = "/sys/bus/mmc/devices";
+ DIR *d;
+ struct dirent *ent;
+ bool header_printed = false;
+
+ d = opendir(bus_path);
+ if (!d) {
+ fprintf(stderr, "Cannot open %s\n", bus_path);
+ return -1;
+ }
+
+ struct config cfg = {};
+
+ while ((ent = readdir(d)) != NULL) {
+ char devpath[PATH_MAX];
+ char resolved[PATH_MAX];
+ char *type, *cid, *blkdev;
+
+ if (!strchr(ent->d_name, ':'))
+ continue;
+
+ snprintf(devpath, sizeof(devpath), "%s/%s", bus_path, ent->d_name);
+ if (realpath(devpath, resolved) == NULL)
+ continue;
+
+ type = read_file_at(resolved, "type");
+ if (!type)
+ continue;
+
+ cid = read_file_at(resolved, "cid");
+ if (!cid) {
+ free(type);
+ continue;
+ }
+
+ blkdev = find_block_devname(resolved);
+ cfg.bus = strcmp(type, "MMC") ? SD : MMC;
+
+ if (!header_printed) {
+ printf("%-14s %-14s %-5s%-20s %-10s %-5s %-12s %s\n",
+ "DEVICE", "DEV", "TYPE", "MANUFACTURER", "PRODUCT",
+ "REV", "SERIAL", "DATE");
+ header_printed = true;
+ }
+
+ print_list_entry(&cfg, ent->d_name, blkdev, cid);
+
+ free(blkdev);
+ free(cid);
+ free(type);
+ }
+
+ closedir(d);
+ return 0;
+}
+
+
int do_read_csd(int argc, char **argv)
{
return do_read_reg(argc, argv, CSD);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/5] mmc-utils: lsmmc: Use external .ids files for manufacturer lookup
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
2026-05-15 21:37 ` [PATCH v2 1/5] mmc-utils: lsmmc: Refactor CID parsing into shared structs Torstein Eide
@ 2026-05-15 21:37 ` Torstein Eide
2026-05-15 21:37 ` [PATCH v2 3/5] mmc-utils: lsmmc: Accept /dev and /sys/block paths for register reads Torstein Eide
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
Similar to pci.ids and usb.ids, move the statically defined manufacturer
ID arrays out of the source and into external sdcard.ids and
multimediacard.ids files. This allows other programs to use the same
database without an API, ABI, or cmd interface.
Make the program able to read from the base directory to test without
installing.
Make it possible from the Makefile to change the install directory of
the database.
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
---
Makefile | 8 +-
lsmmc.c | 252 +++++++++++++--------------------------------
multimediacard.ids | 16 +++
sdcard.ids | 23 +++++
4 files changed, 116 insertions(+), 183 deletions(-)
create mode 100644 multimediacard.ids
create mode 100644 sdcard.ids
diff --git a/Makefile b/Makefile
index c0284bb..11685b9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,9 @@
CC ?= gcc
GIT_VERSION := "$(shell git describe --abbrev=6 --always --tags)"
AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2 \
- -DVERSION=\"$(GIT_VERSION)\"
+ -DVERSION=\"$(GIT_VERSION)\" \
+ -DSD_IDS_PATH=\"$(idsdir)/sdcard.ids\" \
+ -DMMC_IDS_PATH=\"$(idsdir)/multimediacard.ids\"
CFLAGS ?= -g -O2
objects = \
mmc.o \
@@ -19,6 +21,7 @@ override CFLAGS := $(CHECKFLAGS) $(AM_CFLAGS) $(CFLAGS)
INSTALL = install
prefix ?= /usr/local
bindir = $(prefix)/bin
+idsdir ?= /usr/share/misc
LIBS=
RESTORE_LIBS=
mandir = /usr/share/man
@@ -55,6 +58,9 @@ install: $(progs)
$(INSTALL) $(progs) $(DESTDIR)$(bindir)
$(INSTALL) -m755 -d $(DESTDIR)$(mandir)/man1
$(INSTALL) -m 644 mmc.1 $(DESTDIR)$(mandir)/man1
+ $(INSTALL) -m755 -d $(DESTDIR)$(idsdir)
+ $(INSTALL) -m 644 sdcard.ids $(DESTDIR)$(idsdir)
+ $(INSTALL) -m 644 multimediacard.ids $(DESTDIR)$(idsdir)
-include $(foreach obj,$(objects), $(dir $(obj))/.$(notdir $(obj)).d)
diff --git a/lsmmc.c b/lsmmc.c
index 8155a5c..1c55b86 100644
--- a/lsmmc.c
+++ b/lsmmc.c
@@ -56,6 +56,13 @@
#define IDS_MAX 256
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#ifndef SD_IDS_PATH
+#define SD_IDS_PATH "/usr/share/misc/sdcard.ids"
+#endif
+#ifndef MMC_IDS_PATH
+#define MMC_IDS_PATH "/usr/share/misc/multimediacard.ids"
+#endif
+
enum bus_type {
MMC = 1,
SD,
@@ -75,157 +82,6 @@ enum REG_TYPE {
SCR,
};
-struct ids_database {
- int id;
- char *manufacturer;
-};
-
-static struct ids_database sd_database[] = {
- {
- .id = 0x01,
- .manufacturer = "Panasonic",
- },
- {
- .id = 0x02,
- .manufacturer = "Toshiba/Kingston/Viking",
- },
- {
- .id = 0x03,
- .manufacturer = "SanDisk",
- },
- {
- .id = 0x08,
- .manufacturer = "Silicon Power",
- },
- {
- .id = 0x18,
- .manufacturer = "Infineon",
- },
- {
- .id = 0x1b,
- .manufacturer = "Transcend/Samsung",
- },
- {
- .id = 0x1c,
- .manufacturer = "Transcend",
- },
- {
- .id = 0x1d,
- .manufacturer = "Corsair/AData",
- },
- {
- .id = 0x1e,
- .manufacturer = "Transcend",
- },
- {
- .id = 0x1f,
- .manufacturer = "Kingston",
- },
- {
- .id = 0x27,
- .manufacturer = "Delkin/Phison",
- },
- {
- .id = 0x28,
- .manufacturer = "Lexar",
- },
- {
- .id = 0x30,
- .manufacturer = "SanDisk",
- },
- {
- .id = 0x31,
- .manufacturer = "Silicon Power",
- },
- {
- .id = 0x33,
- .manufacturer = "STMicroelectronics",
- },
- {
- .id = 0x41,
- .manufacturer = "Kingston",
- },
- {
- .id = 0x6f,
- .manufacturer = "STMicroelectronics",
- },
- {
- .id = 0x74,
- .manufacturer = "Transcend",
- },
- {
- .id = 0x76,
- .manufacturer = "Patriot",
- },
- {
- .id = 0x82,
- .manufacturer = "Gobe/Sony",
- },
- {
- .id = 0x89,
- .manufacturer = "Unknown",
- },
-};
-
-static struct ids_database mmc_database[] = {
- {
- .id = 0x00,
- .manufacturer = "SanDisk",
- },
- {
- .id = 0x02,
- .manufacturer = "Kingston/SanDisk",
- },
- {
- .id = 0x03,
- .manufacturer = "Toshiba",
- },
- {
- .id = 0x05,
- .manufacturer = "Unknown",
- },
- {
- .id = 0x06,
- .manufacturer = "Unknown",
- },
- {
- .id = 0x11,
- .manufacturer = "Toshiba",
- },
- {
- .id = 0x13,
- .manufacturer = "Micron",
- },
- {
- .id = 0x15,
- .manufacturer = "Samsung/SanDisk/LG",
- },
- {
- .id = 0x37,
- .manufacturer = "KingMax",
- },
- {
- .id = 0x44,
- .manufacturer = "ATP",
- },
- {
- .id = 0x45,
- .manufacturer = "SanDisk Corporation",
- },
- {
- .id = 0x2c,
- .manufacturer = "Kingston",
- },
- {
- .id = 0x70,
- .manufacturer = "Kingston",
- },
- {
- .id = 0xfe,
- .manufacturer = "Micron",
- },
-};
-
/* Command line parsing functions */
static void usage(char *progname)
{
@@ -321,37 +177,71 @@ static int parse_opts(int argc, char **argv, struct config *config)
static char *get_manufacturer(struct config *config, unsigned int manid)
{
- struct ids_database *db;
- unsigned int ids_cnt;
- int i;
+ char local_path[PATH_MAX];
+ const char *system_path;
+ FILE *f;
+ char name[256];
+ char line[256];
+ const char *local_name;
- if (config->bus == MMC) {
- db = mmc_database;
- ids_cnt = ARRAY_SIZE(mmc_database);
- } else {
- db = sd_database;
- ids_cnt = ARRAY_SIZE(sd_database);
+ local_name = (config->bus == MMC) ? "multimediacard.ids" : "sdcard.ids";
+ system_path = (config->bus == MMC) ? MMC_IDS_PATH : SD_IDS_PATH;
+ snprintf(local_path, sizeof(local_path), "%s", local_name);
+
+ f = fopen(local_path, "r");
+ if (!f)
+ f = fopen(system_path, "r");
+ if (!f) {
+ static bool warned;
+
+ if (!warned) {
+ fprintf(stderr, "Warning: ids file not found (%s or %s)\n",
+ local_path, system_path);
+ warned = true;
+ }
+ return NULL;
}
- for (i = 0; i < ids_cnt; i++) {
- if (db[i].id == manid)
- return db[i].manufacturer;
+ while (fgets(line, sizeof(line), f)) {
+ unsigned int id;
+ char *nl;
+
+ nl = strchr(line, '\n');
+ if (nl)
+ *nl = '\0';
+
+ if (line[0] == '#' || line[0] == '\0')
+ continue;
+
+ if (sscanf(line, "0x%x %255[^\n]", &id, name) == 2) {
+ if (id == manid) {
+ fclose(f);
+ return strdup(name);
+ }
+ }
}
+ fclose(f);
return NULL;
}
/* MMC/SD file parsing functions */
-static char *read_file(char *name)
+static char *read_file_at(const char *dir, const char *name)
{
+ char path[PATH_MAX];
char line[4096];
char *preparsed, *start = line;
int len;
FILE *f;
- f = fopen(name, "r");
+ if (snprintf(path, sizeof(path), "%s/%s", dir, name) >= PATH_MAX) {
+ fprintf(stderr, "Path too long: %s/%s\n", dir, name);
+ return NULL;
+ }
+
+ f = fopen(path, "r");
if (!f) {
- fprintf(stderr, "Could not open MMC/SD file '%s'.\n", name);
+ fprintf(stderr, "Could not open MMC/SD file '%s'.\n", path);
return NULL;
}
@@ -359,20 +249,20 @@ static char *read_file(char *name)
if (!preparsed) {
if (ferror(f))
fprintf(stderr, "Could not read MMC/SD file '%s'.\n",
- name);
+ path);
else
fprintf(stderr,
"Could not read data from MMC/SD file '%s'.\n",
- name);
+ path);
if (fclose(f))
fprintf(stderr, "Could not close MMC/SD file '%s'.\n",
- name);
+ path);
return NULL;
}
if (fclose(f)) {
- fprintf(stderr, "Could not close MMC/SD file '%s'.\n", name);
+ fprintf(stderr, "Could not close MMC/SD file '%s'.\n", path);
return NULL;
}
@@ -593,6 +483,8 @@ static void print_sd_cid(struct config *config, char *cid)
printf("manufacturing date: %u %s\n", 2000 + c.mdt_year,
months[c.mdt_month]);
}
+
+ free(manufacturer);
}
static void print_mmc_cid(struct config *config, char *cid)
@@ -654,6 +546,8 @@ static void print_mmc_cid(struct config *config, char *cid)
printf("manufacturing date: %u %s\n", 1997 + c.mdt_year,
months[c.mdt_month]);
}
+
+ free(manufacturer);
}
static void print_sd_csd(struct config *config, char *csd)
@@ -2202,12 +2096,12 @@ static int process_reg_from_file(struct config *config, enum REG_TYPE reg)
switch (reg) {
case CID:
- reg_content = read_file("cid");
+ reg_content = read_file_at(config->dir, "cid");
ret = process_reg(config, reg_content, CID);
break;
case CSD:
- reg_content = read_file("csd");
+ reg_content = read_file_at(config->dir, "csd");
ret = process_reg(config, reg_content, CSD);
break;
@@ -2215,7 +2109,7 @@ static int process_reg_from_file(struct config *config, enum REG_TYPE reg)
if (config->bus != SD)
break;
- reg_content = read_file("scr");
+ reg_content = read_file_at(config->dir, "scr");
ret = process_reg(config, reg_content, SCR);
break;
@@ -2234,19 +2128,13 @@ static int process_dir(struct config *config, enum REG_TYPE reg)
char *type = NULL;
int ret = 0;
- if (chdir(config->dir) < 0) {
- fprintf(stderr,
- "MMC/SD information directory '%s' does not exist.\n",
- config->dir);
- return -1;
- }
-
- type = read_file("type");
+ type = read_file_at(config->dir, "type");
if (!type) {
fprintf(stderr,
"Could not read card interface type in directory '%s'.\n",
config->dir);
- return -1;
+ ret = -1;
+ goto err;
}
if (strcmp(type, "MMC") && strcmp(type, "SD")) {
diff --git a/multimediacard.ids b/multimediacard.ids
new file mode 100644
index 0000000..0c91118
--- /dev/null
+++ b/multimediacard.ids
@@ -0,0 +1,16 @@
+# MMC manufacturer IDs
+# This file is maintained at: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git
+0x00 SanDisk
+0x02 Kingston/SanDisk
+0x03 Toshiba
+0x05 Unknown
+0x06 Unknown
+0x11 Toshiba
+0x13 Micron
+0x15 Samsung/SanDisk/LG
+0x37 KingMax
+0x44 ATP
+0x45 SanDisk Corporation
+0x2c Kingston
+0x70 Kingston
+0xfe Micron
diff --git a/sdcard.ids b/sdcard.ids
new file mode 100644
index 0000000..cbddcae
--- /dev/null
+++ b/sdcard.ids
@@ -0,0 +1,23 @@
+# SD card (secure digital) manufacturer IDs
+# This file is maintained at: https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git
+0x01 Panasonic
+0x02 Toshiba/Kingston/Viking
+0x03 SanDisk
+0x08 Silicon Power
+0x18 Infineon
+0x1b Transcend/Samsung
+0x1c Transcend
+0x1d Corsair/AData
+0x1e Transcend
+0x1f Kingston
+0x27 Delkin/Phison
+0x28 Lexar
+0x30 SanDisk
+0x31 Silicon Power
+0x33 STMicroelectronics
+0x41 Kingston
+0x6f STMicroelectronics
+0x74 Transcend
+0x76 Patriot
+0x82 Gobe/Sony
+0x89 Unknown
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 3/5] mmc-utils: lsmmc: Accept /dev and /sys/block paths for register reads
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
2026-05-15 21:37 ` [PATCH v2 1/5] mmc-utils: lsmmc: Refactor CID parsing into shared structs Torstein Eide
2026-05-15 21:37 ` [PATCH v2 2/5] mmc-utils: lsmmc: Use external .ids files for manufacturer lookup Torstein Eide
@ 2026-05-15 21:37 ` Torstein Eide
2026-05-15 21:37 ` [PATCH v2 4/5] mmc-utils: lsmmc: Add mmc list command Torstein Eide
2026-05-15 21:37 ` [PATCH v2 5/5] mmc-utils: Add bash completion Torstein Eide
4 siblings, 0 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
Commands like 'cid read' previously required a raw sysfs device path
such as /sys/bus/mmc/devices/mmc0:0001/. Most users know their block
device as /dev/mmcblk0 or /sys/block/mmcblk0.
Add resolve_dev_path() which follows /sys/class/block/<dev>/device via
realpath() to reach the canonical sysfs device directory. Paths that
start with /dev/ or /sys/block/ are resolved automatically in
do_read_reg() before processing, and the resolved sysfs path is printed
so the caller can see which device was matched.
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
---
docs/HOWTO.rst | 59 +++++++++++++++++++++++++++++++++++++++++--------
lsmmc.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+), 9 deletions(-)
diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst
index 7a27a50..76214a3 100644
--- a/docs/HOWTO.rst
+++ b/docs/HOWTO.rst
@@ -35,19 +35,60 @@ Running mmc-utils
Set user area write protection.
``csd read [-h] [-v] [-b bus_type] [-r register] <device path>``
- Print CSD data from <device path>. The device path should specify the csd sysfs file directory.
- if [bus_type] is passed (mmc or sd) the [register] content must be passed as well, and no need for device path.
- it is useful for cases we are getting the register value without having the actual platform.
+ Print CSD data from <device path>. The device path may be one of the following:
+ - sysfs device directory (/sys/devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001)
+ - device node (/dev/mmcblkN)
+ - sysfs block device entry (/sys/block/mmcblkN)
+ /dev/ and /sys/block/ paths are resolved to the sysfs device
+ directory automatically and the resolved path is printed.
+ If [-b bus_type] is passed (mmc or sd) the [-r register] content must
+ be passed as well, and no device path is required. Useful when the
+ register value is known without access to the actual hardware.
+
+ Example::
+
+ $ mmc csd read /dev/mmcblk0
+ sysfs: /sys/devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001
+ CSD Register: 00000000...
``cid read <device path>``
- Print CID data from <device path>. The device path should specify the cid sysfs file directory.
- if [bus_type] is passed (mmc or sd) the [register] content must be passed as well, and no need for device path.
- it is useful for cases we are getting the register value without having the actual platform.
+ Print CID data from <device path>. The device path may be one of the following:
+ - sysfs device directory (/sys/devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001)
+ - device node (/dev/mmcblkN)
+ - sysfs block device entry (/sys/block/mmcblkN)
+ /dev/ and /sys/block/ paths are resolved to the sysfs device
+ directory automatically and the resolved path is printed.
+ If [-b bus_type] is passed (mmc or sd) the [-r register] content must
+ be passed as well, and no device path is required. Useful when the
+ register value is known without access to the actual hardware.
+
+ Example::
+
+ $ mmc cid read /dev/mmcblk0
+ sysfs: /sys/devices/platform/fe310000.mmc/mmc_host/mmc0/mmc0:0001
+ Manufacturer ID: 0x15
+ OEM ID: 0x0100
+ Product name: MAG4FA
+ Product revision: 1.0
+ Serial number: 0x1a2b3c4d
+ Manufacturing date: 01/2021
``scr read <device path>``
- Print SCR data from <device path>. The device path should specify the scr sysfs file directory.
- if [bus_type] is passed (mmc or sd) the [register] content must be passed as well, and no need for device path.
- it is useful for cases we are getting the register value without having the actual platform.
+ Print SCR data from <device path>. The device path may be one of the following:
+ - sysfs device directory (/sys/devices/platform/fe320000.mmc/mmc_host/mmc1/mmc1:aaaa)
+ - device node (/dev/mmcblkN)
+ - sysfs block device entry (/sys/block/mmcblkN)
+ /dev/ and /sys/block/ paths are resolved to the sysfs device
+ directory automatically and the resolved path is printed.
+ If [-b bus_type] is passed (mmc or sd) the [-r register] content must
+ be passed as well, and no device path is required. Useful when the
+ register value is known without access to the actual hardware.
+
+ Example::
+
+ $ mmc scr read /dev/mmcblk1
+ sysfs: /sys/devices/platform/fe320000.mmc/mmc_host/mmc1/mmc1:aaaa
+ SCR Register: 0235800000000000
``ffu <image name> <device> [chunk-bytes]``
Default mode. Run Field Firmware Update with `<image name>` on `<device>`. `[chunk-bytes]` is optional and defaults to its max - 512k. Should be in decimal bytes and sector aligned.
diff --git a/lsmmc.c b/lsmmc.c
index 1c55b86..58d4609 100644
--- a/lsmmc.c
+++ b/lsmmc.c
@@ -281,6 +281,51 @@ static char *read_file_at(const char *dir, const char *name)
return strdup(start);
}
+static char *resolve_dev_path(const char *path)
+{
+ const char *devname = strrchr(path, '/');
+ char basedev[NAME_MAX + 1];
+ char syspath[PATH_MAX];
+ char resolved[PATH_MAX];
+ char *p, *type;
+
+ devname = devname ? devname + 1 : path;
+ if (*devname == '\0')
+ return NULL;
+
+ /* strip partition suffix: mmcblk0p1 -> mmcblk0 */
+ strncpy(basedev, devname, sizeof(basedev) - 1);
+ basedev[sizeof(basedev) - 1] = '\0';
+ p = basedev + strlen(basedev);
+ while (p > basedev && isdigit((unsigned char)p[-1]))
+ p--;
+ if (p > basedev && p[-1] == 'p')
+ p[-1] = '\0';
+
+ if (snprintf(syspath, sizeof(syspath), "/sys/class/block/%s/device",
+ basedev) >= PATH_MAX)
+ return NULL;
+
+ if (realpath(syspath, resolved) == NULL) {
+ fprintf(stderr, "Cannot resolve '%s': %s\n", path, strerror(errno));
+ return NULL;
+ }
+
+ type = read_file_at(resolved, "type");
+ if (!type) {
+ fprintf(stderr, "'%s' does not appear to be an MMC/SD device\n", path);
+ return NULL;
+ }
+ if (strcmp(type, "MMC") && strcmp(type, "SD")) {
+ fprintf(stderr, "'%s': unknown device type '%s'\n", path, type);
+ free(type);
+ return NULL;
+ }
+ free(type);
+
+ return strdup(resolved);
+}
+
/* Hexadecimal string parsing functions */
static char *to_binstr(char *hexstr)
{
@@ -2162,6 +2207,21 @@ static int do_read_reg(int argc, char **argv, enum REG_TYPE reg)
if (ret)
return ret;
+ if (cfg.dir &&
+ (strncmp(cfg.dir, "/dev/", 5) == 0 ||
+ strncmp(cfg.dir, "/sys/block/", 11) == 0)) {
+ char *sysfs = resolve_dev_path(cfg.dir);
+
+ if (!sysfs) {
+ free(cfg.dir);
+ return -1;
+ }
+
+ free(cfg.dir);
+ cfg.dir = sysfs;
+ printf("sysfs: %s\n", cfg.dir);
+ }
+
if (cfg.dir) {
ret = process_dir(&cfg, reg);
free(cfg.dir);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 4/5] mmc-utils: lsmmc: Add mmc list command
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
` (2 preceding siblings ...)
2026-05-15 21:37 ` [PATCH v2 3/5] mmc-utils: lsmmc: Accept /dev and /sys/block paths for register reads Torstein Eide
@ 2026-05-15 21:37 ` Torstein Eide
2026-05-15 21:37 ` [PATCH v2 5/5] mmc-utils: Add bash completion Torstein Eide
4 siblings, 0 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
Add a 'mmc list' command that scans /sys/bus/mmc/devices/, resolves
each card's sysfs path, and prints a one-line summary per device.
find_block_devname() maps a sysfs device path back to its mmcblkN
name by scanning /sys/class/block/mmcblkN/device symlinks.
print_list_entry() parses the CID register and formats a fixed-width
table row with device name, /dev path, bus type, manufacturer, product,
revision, serial number, and manufacturing date.
The header is printed once before the first result. Devices without a
readable cid file are skipped silently via access() so unrelated sysfs
entries do not produce spurious error messages.
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
---
docs/HOWTO.rst | 12 ++++++++++++
lsmmc.c | 34 +++++++++++++++++++++++++++++++++-
mmc.c | 6 +++++-
mmc_cmds.h | 1 +
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst
index 76214a3..167a153 100644
--- a/docs/HOWTO.rst
+++ b/docs/HOWTO.rst
@@ -90,6 +90,18 @@ Running mmc-utils
sysfs: /sys/devices/platform/fe320000.mmc/mmc_host/mmc1/mmc1:aaaa
SCR Register: 0235800000000000
+ ``list``
+ List all MMC/SD devices present on the system. Output is a table with
+ columns: DEVICE (sysfs name), DEV (/dev path), TYPE (MMC or SD),
+ MANUFACTURER, PRODUCT, REV, SERIAL, and DATE.
+
+ Example::
+
+ $ mmc list
+ DEVICE DEV TYPE MANUFACTURER PRODUCT REV SERIAL DATE
+ mmc0:0001 /dev/mmcblk0 MMC Samsung MAG4FA 1.0 0x1a2b3c4d 2021-jan
+ mmc1:aaaa /dev/mmcblk1 SD SanDisk SP32G 8.0 0x5e6f7a8b 2020-mar
+
``ffu <image name> <device> [chunk-bytes]``
Default mode. Run Field Firmware Update with `<image name>` on `<device>`. `[chunk-bytes]` is optional and defaults to its max - 512k. Should be in decimal bytes and sector aligned.
diff --git a/lsmmc.c b/lsmmc.c
index 58d4609..0458d1b 100644
--- a/lsmmc.c
+++ b/lsmmc.c
@@ -225,6 +225,39 @@ static char *get_manufacturer(struct config *config, unsigned int manid)
return NULL;
}
+static char *find_block_devname(const char *sysfs_devpath)
+{
+ DIR *d;
+ struct dirent *ent;
+
+ d = opendir("/sys/class/block");
+ if (!d)
+ return NULL;
+
+ while ((ent = readdir(d)) != NULL) {
+ char linkpath[PATH_MAX];
+ char resolved[PATH_MAX];
+
+ if (strncmp(ent->d_name, "mmcblk", 6) != 0)
+ continue;
+ if (strchr(ent->d_name + 6, 'p'))
+ continue;
+
+ snprintf(linkpath, sizeof(linkpath),
+ "/sys/class/block/%s/device", ent->d_name);
+ if (realpath(linkpath, resolved) == NULL)
+ continue;
+ if (strcmp(resolved, sysfs_devpath) == 0) {
+ closedir(d);
+ return strdup(ent->d_name);
+ }
+ }
+
+ closedir(d);
+ return NULL;
+}
+
+
/* MMC/SD file parsing functions */
static char *read_file_at(const char *dir, const char *name)
{
@@ -2335,7 +2368,6 @@ int do_list(int nargs, char **argv)
return 0;
}
-
int do_read_csd(int argc, char **argv)
{
return do_read_reg(argc, argv, CSD);
diff --git a/mmc.c b/mmc.c
index 077e901..01ad181 100644
--- a/mmc.c
+++ b/mmc.c
@@ -292,6 +292,11 @@ static struct Command commands[] = {
"4. The MMC will perform a soft reset, if your system cannot handle that do not use the boot operation from mmc-utils.\n",
NULL
},
+ { do_list, 0,
+ "list", "\n"
+ "List all MMC/SD devices with their /dev path and CID info.",
+ NULL
+ },
{ NULL, 0, NULL, NULL }
};
@@ -567,4 +572,3 @@ int main(int ac, char **av )
exit(func(nargs, args));
}
-
diff --git a/mmc_cmds.h b/mmc_cmds.h
index 407cbe6..508776c 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -49,6 +49,7 @@ int do_opt_ffu4(int nargs, char **argv);
int do_read_scr(int argc, char **argv);
int do_read_cid(int argc, char **argv);
int do_read_csd(int argc, char **argv);
+int do_list(int nargs, char **argv);
int do_erase(int nargs, char **argv);
int do_general_cmd_read(int nargs, char **argv);
int do_softreset(int nargs, char **argv);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 5/5] mmc-utils: Add bash completion
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
` (3 preceding siblings ...)
2026-05-15 21:37 ` [PATCH v2 4/5] mmc-utils: lsmmc: Add mmc list command Torstein Eide
@ 2026-05-15 21:37 ` Torstein Eide
4 siblings, 0 replies; 6+ messages in thread
From: Torstein Eide @ 2026-05-15 21:37 UTC (permalink / raw)
To: linux-mmc; +Cc: Torstein Eide
Add a bash completion script in completion/mmc that completes subcommands
and /dev/mmcblkN device paths for all mmc commands.
The completion directory is named 'completion/' rather than
'bash-completion/' so shell-specific scripts for other shells (zsh,
fish, etc.) can be placed alongside it without renaming.
Add bashcompletiondir variable to the Makefile (defaulting to the
standard /usr/share/bash-completion/completions) and install the
script as part of 'make install'. The install path can be overridden
at build time with make bashcompletiondir=<path>.
Signed-off-by: Torstein Eide <torsteine+linux@gmail.com>
---
Makefile | 5 +++-
completion/mmc | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++
docs/HOWTO.rst | 6 +++++
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 completion/mmc
diff --git a/Makefile b/Makefile
index 11685b9..e447e54 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2 \
-DSD_IDS_PATH=\"$(idsdir)/sdcard.ids\" \
-DMMC_IDS_PATH=\"$(idsdir)/multimediacard.ids\"
CFLAGS ?= -g -O2
+idsdir = /usr/share/misc
+bashcompletiondir = /usr/share/bash-completion/completions
objects = \
mmc.o \
mmc_cmds.o \
@@ -21,7 +23,6 @@ override CFLAGS := $(CHECKFLAGS) $(AM_CFLAGS) $(CFLAGS)
INSTALL = install
prefix ?= /usr/local
bindir = $(prefix)/bin
-idsdir ?= /usr/share/misc
LIBS=
RESTORE_LIBS=
mandir = /usr/share/man
@@ -61,6 +62,8 @@ install: $(progs)
$(INSTALL) -m755 -d $(DESTDIR)$(idsdir)
$(INSTALL) -m 644 sdcard.ids $(DESTDIR)$(idsdir)
$(INSTALL) -m 644 multimediacard.ids $(DESTDIR)$(idsdir)
+ $(INSTALL) -m755 -d $(DESTDIR)$(bashcompletiondir)
+ $(INSTALL) -m 644 completion/mmc $(DESTDIR)$(bashcompletiondir)/mmc
-include $(foreach obj,$(objects), $(dir $(obj))/.$(notdir $(obj)).d)
diff --git a/completion/mmc b/completion/mmc
new file mode 100644
index 0000000..c759972
--- /dev/null
+++ b/completion/mmc
@@ -0,0 +1,62 @@
+_mmc_complete() {
+ local cur prev words cword
+ _init_completion || return
+
+ local devices
+ devices=$(compgen -G "/dev/mmcblk*" 2>/dev/null | grep -E '^/dev/mmcblk[0-9]+$')
+
+ case $cword in
+ 1)
+ local verbs="extcsd writeprotect disable gp enh_area \
+ write_reliability status bootpart bootbus hwreset bkops \
+ sanitize rpmb cache csd cid scr ffu opt_ffu1 opt_ffu2 \
+ opt_ffu3 opt_ffu4 erase gen_cmd softreset preidle \
+ boot_operation list"
+ COMPREPLY=($(compgen -W "$verbs" -- "$cur"))
+ ;;
+ 2)
+ case $prev in
+ extcsd)
+ COMPREPLY=($(compgen -W "read write" -- "$cur")) ;;
+ writeprotect)
+ COMPREPLY=($(compgen -W "boot user" -- "$cur")) ;;
+ bootpart)
+ COMPREPLY=($(compgen -W "enable" -- "$cur")) ;;
+ bootbus)
+ COMPREPLY=($(compgen -W "set" -- "$cur")) ;;
+ hwreset)
+ COMPREPLY=($(compgen -W "enable disable" -- "$cur")) ;;
+ bkops)
+ COMPREPLY=($(compgen -W "enable" -- "$cur")) ;;
+ rpmb)
+ COMPREPLY=($(compgen -W "read-counter read-block write-block write-key" -- "$cur")) ;;
+ cache)
+ COMPREPLY=($(compgen -W "enable disable" -- "$cur")) ;;
+ csd|cid|scr)
+ COMPREPLY=($(compgen -W "read" -- "$cur")) ;;
+ gen_cmd)
+ COMPREPLY=($(compgen -W "read" -- "$cur")) ;;
+ status)
+ COMPREPLY=($(compgen -W "get" -- "$cur")) ;;
+ gp)
+ COMPREPLY=($(compgen -W "create" -- "$cur")) ;;
+ enh_area|write_reliability)
+ COMPREPLY=($(compgen -W "set" -- "$cur")) ;;
+ sanitize|softreset|preidle|ffu|opt_ffu1|opt_ffu2|opt_ffu3|opt_ffu4|erase|boot_operation)
+ COMPREPLY=($(compgen -W "$devices" -- "$cur")) ;;
+ esac
+ ;;
+ 3)
+ case ${words[1]} in
+ writeprotect)
+ COMPREPLY=($(compgen -W "get set" -- "$cur")) ;;
+ csd|cid|scr|extcsd|gen_cmd|status|bootpart|bootbus|hwreset|bkops|cache|gp|enh_area|write_reliability)
+ COMPREPLY=($(compgen -W "$devices" -- "$cur")) ;;
+ esac
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$devices" -- "$cur")) ;;
+ esac
+}
+
+complete -F _mmc_complete mmc
diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst
index 167a153..460d016 100644
--- a/docs/HOWTO.rst
+++ b/docs/HOWTO.rst
@@ -2,6 +2,12 @@
Running mmc-utils
-----------------
+**Bash completion**
+ Source ``completion/mmc`` to enable tab-completion for all subcommands and
+ device paths. When installed via ``make install``, the file is placed in
+ ``$(bashcompletiondir)`` (default: ``/usr/share/bash-completion/completions/mmc``)
+ and loaded automatically by bash-completion.
+
**Name**
mmc - a tool for configuring MMC storage devices
**Synopsis**
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-15 21:38 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-15 21:37 [PATCH v2 0/5] mmc-utils: improve lsmmc usability Torstein Eide
2026-05-15 21:37 ` [PATCH v2 1/5] mmc-utils: lsmmc: Refactor CID parsing into shared structs Torstein Eide
2026-05-15 21:37 ` [PATCH v2 2/5] mmc-utils: lsmmc: Use external .ids files for manufacturer lookup Torstein Eide
2026-05-15 21:37 ` [PATCH v2 3/5] mmc-utils: lsmmc: Accept /dev and /sys/block paths for register reads Torstein Eide
2026-05-15 21:37 ` [PATCH v2 4/5] mmc-utils: lsmmc: Add mmc list command Torstein Eide
2026-05-15 21:37 ` [PATCH v2 5/5] mmc-utils: Add bash completion Torstein Eide
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.