Ognian / sdmon

get SD card health data
https://www.ogi-it.com/portfolio/sdmon/
GNU General Public License v2.0
100 stars 18 forks source link

Patch to support FreeBSD #26

Closed agaviola80 closed 7 months ago

agaviola80 commented 8 months ago

Hi,

I would like to submit two patches, the sdmon.c.patch and Makefile.freebsd.patch to make it work with FreeBSD particularly 14.0-RELEASE. My patches are made using the sdmon-latest source using my Raspberry Pi 3B SBC and it has been tested with my SanDisk Industrial microSD card and Kingston SDCIT2 Industrial microSD card.

root@sandiski:~ # cat /home/freebsd/sdmon-latest/src/sdmon.c.patch

--- /home/freebsd/sdmon-latest/src/sdmon.c.orig 2024-03-16 22:21:47.986519000 +0800
+++ /home/freebsd/sdmon-latest/src/sdmon.c      2024-03-16 22:21:47.986195000 +0800
@@ -17,7 +17,9 @@

 // this include defines MMC_BLOCK_MAJOR the magic number for the calculation of
 // MMC_IOC_CMD
+#if defined(__linux__)
 #include <linux/major.h>
+#endif

 // since this include/linux/mmc/core.h is not available we pasted it in here
 /* From kernel linux/mmc/core.h */
@@ -47,7 +49,11 @@
 #include <errno.h>
 // this is the include defining the mmc_ioc_cmd struct and mmc_ioc_cmd_set_data
 // helper etc ...
+#if defined(__linux__)
 #include <linux/mmc/ioctl.h>
+#elif defined(__FreeBSD__)
+#include <dev/mmc/mmc_ioctl.h>
+#endif

 #include <time.h>

@@ -62,7 +68,11 @@
   struct mmc_ioc_cmd idata;

   memset(&idata, 0, sizeof(idata));
+#if defined(__linux__)
   memset(lba_block_data, 0, sizeof(__u8) * SD_BLOCK_SIZE);
+#elif defined(__FreeBSD__)
+  memset(lba_block_data, 0, sizeof(SD_BLOCK_SIZE));
+#endif

   idata.write_flag = 0;
   idata.opcode = SD_GEN_CMD;
@@ -83,7 +93,11 @@
   char data_out[SD_BLOCK_SIZE];

   memset(&idata, 0, sizeof(idata));
+#if defined(__linux__)
   memset(&data_out[0], 0, sizeof(__u8) * SD_BLOCK_SIZE);
+#elif defined(__FreeBSD__)
+  memset(&data_out[0], 0, sizeof(SD_BLOCK_SIZE));
+#endif

   idata.write_flag = 1;
   idata.opcode = SD_GEN_CMD;
@@ -191,7 +205,11 @@
   }
   printf("\"addTime\": \"%s\",\n", formatBool(addTime));

+#if defined(__linux__)
   fd = open(device, O_RDWR);
+#elif defined(__FreeBSD__)
+  fd = open(device, O_RDONLY);
+#endif
   if (fd < 0) {
     printf("\"error\":\"device open failed\"\n}\n");
     exit(1);

I created a new Makefile.freebsd with contents to be able to compile FreeBSD in its native Clang compiler.

root@sandiski:/home/freebsd/sdmon-latest/src # cat Makefile.freebsd

# (c)2018 ogi-it, Ognian Tschakalov

#VERSION = 0.01
CC      = /usr/bin/cc
CFLAGS  = -Wall -g -D_REENTRANT -DVERSION="\"$(VERSION_STRING)\"" -static
LDFLAGS =

OBJ = sdmon.o
BIN = sdmon

$(BIN): $(OBJ)
        $(CC) $(CFLAGS) -o $(BIN) $(OBJ) $(LDFLAGS)

%.o: %.c
        $(CC) $(CFLAGS) -c $<

.PHONY: clean
clean:
        rm -rf $(BIN) $(OBJ)

and then compile

root@sandiski:/home/freebsd/sdmon-latest/src # make -f Makefile.freebsd

/usr/bin/cc  -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -c sdmon.c -o sdmon.o
/usr/bin/cc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -o sdmon sdmon.o

Here are the test results:

SanDisk Industrial (with and without time)

root@sandiski:/home/freebsd/sdmon-latest/src # ./sdmon /dev/mmcsd0

{
"version": "",
"date": "2024-03-16T23:50:35.000Z",
"device":"/dev/mmcsd0",
"addTime": "false",
"signature":"0x44 0x53",
"SanDisk":"true",
"manufactureYYMMDD": "210415",
"healthStatusPercentUsed": 1,
"featureRevision": "0x1f",
"generationIdentifier": 5,
"productString": "SanDisk                         ",
"powerOnTimes": 165,
"success":true
}

root@sandiski:/home/freebsd/sdmon-latest/src # ./sdmon /dev/mmcsd0 -a

{
"version": "",
"date": "2024-03-16T23:50:38.000Z",
"device":"/dev/mmcsd0",
"addTime": "true",
"signature":"0x44 0x53",
"SanDisk":"true",
"manufactureYYMMDD": "210415",
"healthStatusPercentUsed": 1,
"featureRevision": "0x1f",
"generationIdentifier": 5,
"productString": "SanDisk                         ",
"powerOnTimes": 165,
"success":true
}

Kingston SCIT2 (with and without time)

root@kingston:/tmp/sdmon-latest/src # ./sdmon /dev/mmcsd0

{
"version": "",
"date": "2024-03-16T23:52:22.000Z",
"device":"/dev/mmcsd0",
"addTime": "false",
"read_via_cmd56_arg_1":"read successful but signature 0xff 0xff",
"idata.response[]":"0x00 0x00 0x00 0x00",
"error1":"1st CMD56 CALL FAILED: Bad file descriptor",
"flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"],
"icVersion": ["0x1f","0xc3"],
"fwVersion": [38,240],
"ceNumber": "0x03",
"spareBlockCount": 14,
"initialBadBlockCount": 0,
"goodBlockRatePercent": 100.00,
"totalEraseCount": 0,
"enduranceRemainLifePercent": 100.00,
"avgEraseCount": 0,
"minEraseCount": 0,
"maxEraseCount": 0,
"powerUpCount": 43,
"abnormalPowerOffCount": 0,
"totalRefreshCount": 0,
"productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"],
"laterBadBlockCount": 0,
"success":true
}

root@kingston:/tmp/sdmon-latest/src # ./sdmon /dev/mmcsd0 -a

{
"version": "",
"date": "2024-03-16T23:52:25.000Z",
"device":"/dev/mmcsd0",
"addTime": "true",
"read_via_cmd56_arg_1":"read successful but signature 0xff 0xff",
"idata.response[]":"0x00 0x00 0x00 0x00",
"error1":"1st CMD56 CALL FAILED: Bad file descriptor",
"flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"],
"icVersion": ["0x1f","0xc3"],
"fwVersion": [38,240],
"ceNumber": "0x03",
"spareBlockCount": 14,
"initialBadBlockCount": 0,
"goodBlockRatePercent": 100.00,
"totalEraseCount": 0,
"enduranceRemainLifePercent": 100.00,
"avgEraseCount": 0,
"minEraseCount": 0,
"maxEraseCount": 0,
"powerUpCount": 43,
"abnormalPowerOffCount": 0,
"totalRefreshCount": 0,
"productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"],
"laterBadBlockCount": 0,
"success":true
}

I also perform regression testing with my Ubuntu Linux 16.04 were it is able to compile and run the program with my changes though my microSD is SanDisk Ultra, so it does not support health monitoring.

root@bpi-iot-ros-ai:/home/pi/sdmon-latest/src# make

/usr/bin/gcc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -c sdmon.c
/usr/bin/gcc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -o sdmon sdmon.o

root@bpi-iot-ros-ai:/home/pi/sdmon-latest/src# ./sdmon /dev/mmcblk0

{
"version": "",
"date": "2024-03-17T00:00:01.000Z",
"device":"/dev/mmcblk0",
"addTime": "false",
"read_via_cmd56_arg_1":"not implemented: Connection timed out",
"idata.response[]":"0x00 0x00 0x00 0x00",
"error1":"1st CMD56 CALL FAILED: Connection timed out",
"error2":"2nd CMD56 CALL FAILED: Connection timed out"
}

Lastly, attaching the patches for your reference.

sdmon.c.patch Makefile.freebsd.patch

Thanks, Archimedes

Ognian commented 8 months ago

@agaviola80 Thank you for your contribution! I'll try to merge it soon, but I'm quite busy at the moment...

agaviola80 commented 8 months ago

You're welcome and thanks as well @Ognian! It's okay, no rush from my end.

agaviola80 commented 8 months ago

Hi @Ognian,

Please use this second patch (sdmon.c.patch2) instead of the previous as I fixed the problem on the use of open() O_RDWR flag which causes an issue "error1":"1st CMD56 CALL FAILED: Bad file descriptor" in obtaining data in Kingston industrial microSD card and it's the reason why I use O_RDONLY flag. This time its the same with Linux with O_RDWR flag. Aside from that I got other fixes on the display formatting with long double types. See attached zip'ed file below for this second patch.

root@kingston:/tmp/sdmon-latest/src # cat sdmon.c.patch2
--- /tmp/sdmon-latest/src/sdmon.c.orig  2024-03-27 17:52:49.237357000 +0800
+++ /tmp/sdmon-latest/src/sdmon.c       2024-03-27 18:16:04.075170000 +0800
@@ -17,7 +17,9 @@

 // this include defines MMC_BLOCK_MAJOR the magic number for the calculation of
 // MMC_IOC_CMD
+#if defined(__linux__)
 #include <linux/major.h>
+#endif

 // since this include/linux/mmc/core.h is not available we pasted it in here
 /* From kernel linux/mmc/core.h */
@@ -47,7 +49,11 @@
 #include <errno.h>
 // this is the include defining the mmc_ioc_cmd struct and mmc_ioc_cmd_set_data
 // helper etc ...
+#if defined(__linux__)
 #include <linux/mmc/ioctl.h>
+#elif defined(__FreeBSD__)
+#include <dev/mmc/mmc_ioctl.h>
+#endif

 #include <time.h>

@@ -62,7 +68,11 @@
   struct mmc_ioc_cmd idata;

   memset(&idata, 0, sizeof(idata));
+#if defined(__linux__)
   memset(lba_block_data, 0, sizeof(__u8) * SD_BLOCK_SIZE);
+#elif defined(__FreeBSD__)
+  memset(lba_block_data, 0, sizeof(SD_BLOCK_SIZE));
+#endif

   idata.write_flag = 0;
   idata.opcode = SD_GEN_CMD;
@@ -83,7 +93,11 @@
   char data_out[SD_BLOCK_SIZE];

   memset(&idata, 0, sizeof(idata));
+#if defined(__linux__)
   memset(&data_out[0], 0, sizeof(__u8) * SD_BLOCK_SIZE);
+#elif defined(__FreeBSD__)
+  memset(&data_out[0], 0, sizeof(SD_BLOCK_SIZE));
+#endif

   idata.write_flag = 1;
   idata.opcode = SD_GEN_CMD;
@@ -191,6 +205,12 @@
   }
   printf("\"addTime\": \"%s\",\n", formatBool(addTime));

+  /*
+   * With FreeBSD, make sure kern.geom.debugflags sysctl
+   * flag has value of 16. To change, invoke the command
+   * "sysctl kern.geom.debugflags=16" or write it in the
+   * /etc/sysctl.conf.
+   */
   fd = open(device, O_RDWR);
   if (fd < 0) {
     printf("\"error\":\"device open failed\"\n}\n");
@@ -302,12 +322,19 @@
        printf("\"Total spare blocks cnt\": %d,\n", data_in[24]);
        printf("\"Factory bad blocks cnt\": %d,\n", data_in[25]);
        printf("\"Runtime bad blocks cnt\": %d,\n", data_in[26]);
-       printf("\"Spare utilization rate\": %d%,\n", data_in[27]);
+       printf("\"Spare utilization rate\": %d%%,\n", data_in[27]);
        printf("\"SPOR failure cnt\": %d,\n", bytes_to_int(data_in[28], data_in[29], data_in[30], data_in[31]));
+#if defined(__linux__)
        printf("\"Minimum erase cnt\": %d,\n", (long)((data_in[35]) + (data_in[34]) + (data_in[33]) + data_in[32]));
        printf("\"Maximum erase cnt\": %d,\n", (long)((data_in[39]) + (data_in[38]) + (data_in[37]) + data_in[36]));
        printf("\"Total erase cnt\": %d,\n", (long)((data_in[43]) + (data_in[42]) + (data_in[41]) + data_in[40]));
        printf("\"Average erase cnt\": %d,\n", (long)((data_in[47]) + (data_in[46]) + (data_in[45]) + data_in[44]));
+#elif defined(__FreeBSD__)
+       printf("\"Minimum erase cnt\": %ld,\n", (long)((data_in[35]) + (data_in[34]) + (data_in[33]) + data_in[32]));
+       printf("\"Maximum erase cnt\": %ld,\n", (long)((data_in[39]) + (data_in[38]) + (data_in[37]) + data_in[36]));
+       printf("\"Total erase cnt\": %ld,\n", (long)((data_in[43]) + (data_in[42]) + (data_in[41]) + data_in[40]));
+       printf("\"Average erase cnt\": %ld,\n", (long)((data_in[47]) + (data_in[46]) + (data_in[45]) + data_in[44]));
+#endif
        printf("\"FW version\": %c%c%c%c%c%c%c,\n", data_in[53], data_in[54], data_in[55], data_in[56], data_in[57], data_in[58], data_in[59]);
        close(fd);
        printf("\"success\":true\n}\n");

Here are the outputs with and without time.

root@kingston:/tmp/sdmon-latest/src # ./sdmon /dev/mmcsd0
{
"version": "",
"date": "2024-03-27T18:45:29.000Z",
"device":"/dev/mmcsd0",
"addTime": "false",
"read_via_cmd56_arg_1":"read successful but signature 0xff 0xff",
"idata.response[]":"0x900 0x00 0x00 0x00",
"flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"],
"icVersion": ["0x1f","0xc3"],
"fwVersion": [38,240],
"ceNumber": "0x03",
"spareBlockCount": 14,
"initialBadBlockCount": 34,
"goodBlockRatePercent": 99.41,
"totalEraseCount": 1375,
"enduranceRemainLifePercent": 100.00,
"avgEraseCount": 1,
"minEraseCount": 1,
"maxEraseCount": 11,
"powerUpCount": 56,
"abnormalPowerOffCount": 0,
"totalRefreshCount": 0,
"productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"],
"laterBadBlockCount": 0,
"success":true
}

root@kingston:/tmp/sdmon-latest/src # ./sdmon /dev/mmcsd0 -a
{
"version": "",
"date": "2024-03-27T19:01:51.000Z",
"device":"/dev/mmcsd0",
"addTime": "true",
"read_via_cmd56_arg_1":"read successful but signature 0xff 0xff",
"idata.response[]":"0x900 0x00 0x00 0x00",
"flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"],
"icVersion": ["0x1f","0xc3"],
"fwVersion": [38,240],
"ceNumber": "0x03",
"spareBlockCount": 14,
"initialBadBlockCount": 34,
"goodBlockRatePercent": 99.41,
"totalEraseCount": 1375,
"enduranceRemainLifePercent": 100.00,
"avgEraseCount": 1,
"minEraseCount": 1,
"maxEraseCount": 11,
"powerUpCount": 56,
"abnormalPowerOffCount": 0,
"totalRefreshCount": 0,
"productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"],
"laterBadBlockCount": 0,
"success":true
}

freebsd_sdmon_patch2.zip

Thanks, Archimedes

Ognian commented 7 months ago

not sure if I added everything, since "latest" was ambiguous ...

agaviola80 commented 7 months ago

Hi @Ognian,

Thanks for merging the patch! I just tried the latest release v0.9.0 and it's working with my FreeBSD in Raspberry Pi 3B with Kingston SDCIT2/32GB Industrial microSD card.

  1. Check the source (after downlod and unpacking).

    root@kingston:~/sdmon/sdmon-0.9.0/src # ls -lah
    total 3992
    drwxrwxr-x  2 root wheel  512B Apr 24 12:11 .
    drwxrwxr-x  5 root wheel  512B Apr 23 00:29 ..
    -rw-rw-r--  1 root wheel  325B Apr 23 00:29 Makefile
    -rw-rw-r--  1 root wheel  324B Apr 23 00:29 Makefile.freebsd
    -rwxr-xr-x  1 root wheel  3.8M Apr 24 12:11 sdmon
    -rw-rw-r--  1 root wheel   26K Apr 23 00:29 sdmon.c
  2. Compile source.

    root@kingston:~/sdmon/sdmon-0.9.0/src # make -f Makefile.freebsd
    /usr/bin/cc  -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -c sdmon.c -o sdmon.o
    /usr/bin/cc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -o sdmon sdmon.o
  3. Check the compiled binary (sdmon).

    root@kingston:~/sdmon/sdmon-0.9.0/src # ls -lah
    total 3992
    drwxrwxr-x  2 root wheel  512B Apr 24 12:11 .
    drwxrwxr-x  5 root wheel  512B Apr 23 00:29 ..
    -rw-rw-r--  1 root wheel  325B Apr 23 00:29 Makefile
    -rw-rw-r--  1 root wheel  324B Apr 23 00:29 Makefile.freebsd
    -rwxr-xr-x  1 root wheel  3.8M Apr 24 12:11 sdmon
    -rw-rw-r--  1 root wheel   26K Apr 23 00:29 sdmon.c
    -rw-r--r--  1 root wheel   41K Apr 24 12:11 sdmon.o
  4. Enable disk write in FreeBSD.

    root@kingston:~/sdmon/sdmon-0.9.0/src # sysctl kern.geom.debugflags=16
    kern.geom.debugflags: 0 -> 16
  5. Tests and results.

    
    root@kingston:~/sdmon/sdmon-0.9.0/src # ./sdmon /dev/mmcsd0
    {
    "version": "",
    "date": "2024-04-24T12:13:01.000",
    "device":"/dev/mmcsd0",
    "addTime": "false",
    "read_via_cmd56_arg_1":"read successful but signature 0xff 0xff",
    "idata.response[]":"0x900 0x00 0x00 0x00",
    "flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"],
    "icVersion": ["0x1f","0xc3"],
    "fwVersion": [38,240],
    "ceNumber": "0x03",
    "spareBlockCount": 14,
    "initialBadBlockCount": 34,
    "goodBlockRatePercent": 99.41,
    "totalEraseCount": 1500,
    "enduranceRemainLifePercent": 100.00,
    "avgEraseCount": 1,
    "minEraseCount": 1,
    "maxEraseCount": 13,
    "powerUpCount": 78,
    "abnormalPowerOffCount": 0,
    "totalRefreshCount": 0,
    "productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"],
    "laterBadBlockCount": 0,
    "success":true
    }

root@kingston:~/sdmon/sdmon-0.9.0/src # ./sdmon /dev/mmcsd0 -a { "version": "", "date": "2024-04-24T12:33:51.000", "device":"/dev/mmcsd0", "addTime": "true", "read_via_cmd56_arg_1":"read successful but signature 0xff 0xff", "idata.response[]":"0x900 0x00 0x00 0x00", "flashId": ["0x98","0x3c","0x98","0xb3","0xf6","0xe3","0x08","0x1e","0x00"], "icVersion": ["0x1f","0xc3"], "fwVersion": [38,240], "ceNumber": "0x03", "spareBlockCount": 14, "initialBadBlockCount": 34, "goodBlockRatePercent": 99.41, "totalEraseCount": 1500, "enduranceRemainLifePercent": 100.00, "avgEraseCount": 1, "minEraseCount": 1, "maxEraseCount": 13, "powerUpCount": 78, "abnormalPowerOffCount": 0, "totalRefreshCount": 0, "productMarker": ["0x70","0x53","0x4c","0x43","0x00","0x00","0x00","0x00"], "laterBadBlockCount": 0, "success":true }


Verifying with Linux.

1. Check the source (after downlod and unpacking).

root@agaviola:~/sdmon/sdmon-0.9.0/src# ls -lah total 532K drwxrwxr-x 2 root root 4.0K Apr 24 12:09 . drwxrwxr-x 5 root root 4.0K Apr 23 00:29 .. -rw-rw-r-- 1 root root 325 Apr 23 00:29 Makefile -rw-rw-r-- 1 root root 324 Apr 23 00:29 Makefile.freebsd -rw-rw-r-- 1 root root 26K Apr 23 00:29 sdmon.c

2. Compile source.

root@agaviola:~/sdmon/sdmon-0.9.0/src# make /usr/bin/gcc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -c sdmon.c /usr/bin/gcc -Wall -g -D_REENTRANT -DVERSION="\"\"" -static -o sdmon sdmon.o

4. Check the compiled binary (sdmon).

root@agaviola:~/sdmon/sdmon-0.9.0/src# ls -lah total 532K drwxrwxr-x 2 root root 4.0K Apr 24 12:09 . drwxrwxr-x 5 root root 4.0K Apr 23 00:29 .. -rw-rw-r-- 1 root root 325 Apr 23 00:29 Makefile -rw-rw-r-- 1 root root 324 Apr 23 00:29 Makefile.freebsd -rwxr-xr-x 1 root root 460K Apr 24 12:09 sdmon -rw-rw-r-- 1 root root 26K Apr 23 00:29 sdmon.c -rw-r--r-- 1 root root 27K Apr 24 12:09 sdmon.o

5. Test (sdmon is working but the result have error CMD56 CALL FAILED because this type of SanDisk microSD (Extreme PRO 32GB) is a commercial grade not Industrial, no response to CMD56 request)

root@agaviola:~/sdmon/sdmon-0.9.0/src# ./sdmon /dev/mmcblk0 { "version": "", "date": "2024-04-24T12:09:39.000", "device":"/dev/mmcblk0", "addTime": "false", "read_via_cmd56_arg_1":"not implemented: Connection timed out", "idata.response[]":"0x900 0x00 0x00 0x00", "error2":"2nd CMD56 CALL FAILED: Connection timed out" }



Thanks,
Archimedes
Ognian commented 7 months ago

@agaviola80 thanks a lot for testing! If I find time I'll try to add building the FreeBSD into github actions. A starter link would be this but I don'T know if this is somehow supported under github actions...

agaviola80 commented 7 months ago

You're welcome @Ognian!

Haven't tried that but I found this one https://github.com/vmactions, not sure if this is what you're looking. As far as I know there were some people working on porting the Docker in FreeBSD before but it wasn't pursued for some reason. Virtualization such as Qemu https://wiki.freebsd.org/QemuRecipes and Jails https://wiki.freebsd.org/Jails are the most commonly used.