From ae8042f725f06673823519fa6dfddce13726f69f Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:51:15 +0300 Subject: [PATCH 1/3] sstar: gpio get/set/scan and the IR-cut hint on infinity6c Infinity6C gives every pad a one-byte register (bit 0 level, bit 1 output value, bit 2 output enable, 1 = Hi-Z) at 0x1F207C00 + 2 * riu_off, per the vendor mhal_gpio table, with the two holes in it kept: the RIU offset is not linear in the pad number (pad 24, pad 42). The four vendor-only PAD_ETH_* pads sit outside the gpiochip and stay out. gpio get/set take the plain pad number and print the same mux line as the HiSilicon path; gpio scan prints one Pad: line per GPIO pad with in/out/oe_n decoded; the possible-IR-cut-GPIO heuristic keeps both of its rules with pads in place of groups. Addresses measured on a live SSC37X board and pinned by reginfo_test. --- CMakeLists.txt | 6 +- docs/gpio.md | 13 +++ src/hal/sstar_gpio.c | 86 ++++++++++++++++++ src/hal/sstar_gpio.h | 26 ++++++ src/reginfo.c | 209 +++++++++++++++++++++++++++++++++++++++++++ src/reginfo_test.c | 40 +++++++++ 6 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 src/hal/sstar_gpio.c create mode 100644 src/hal/sstar_gpio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ee04ae3..7076535d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,7 +278,8 @@ set(_common_lib_src_tail # Full set, used by the ipctool executable set(COMMON_LIB_SRC_ALL ${_common_lib_src_head} ${IPCHW_HISI_SRC_ALL} - ${_common_lib_src_tail} ${IPCHW_PADMUX_BACKENDS_ALL}) + ${_common_lib_src_tail} ${IPCHW_PADMUX_BACKENDS_ALL} + src/hal/sstar_gpio.c src/hal/sstar_gpio.h) foreach(_v IN LISTS IPCHW_OPTIONAL_VENDORS) list(APPEND COMMON_LIB_SRC_ALL src/hal/${_v}.c src/hal/${_v}.h) string(TOUPPER ${_v} _V) @@ -295,6 +296,9 @@ foreach(_v IN LISTS _ipchw_selected) "expected 'all', 'none', or a subset of ${IPCHW_OPTIONAL_VENDORS}") endif() list(APPEND COMMON_LIB_SRC src/hal/${_v}.c src/hal/${_v}.h) + if(_v STREQUAL "sstar") + list(APPEND COMMON_LIB_SRC src/hal/sstar_gpio.c src/hal/sstar_gpio.h) + endif() string(TOUPPER ${_v} _V) list(APPEND IPCHW_VENDOR_DEFS IPCHW_VENDOR_${_V}) endforeach() diff --git a/docs/gpio.md b/docs/gpio.md index cc97530a..d3388b7f 100644 --- a/docs/gpio.md +++ b/docs/gpio.md @@ -15,6 +15,14 @@ ipctool gpio set # WRITES: drive the pad `` is accepted in either spelling, `5_6` (group 5, bit 6) or `46` (linear, = group * 8 + bit). Both appear in `gpio scan` output. +On SigmaStar the two spellings are one: pads are already numbered linearly and +there is no `group_bit` form. The pad number is the number the kernel's +gpiochip uses, so `gpio get 23` and `/sys/class/gpio/gpio23` describe the same +wire. The `gpio scan` baseline differs in shape as well — one `Pad:` line per +GPIO pad, decoded from the pad's own one-byte register into what it reads +(`in`), what the SoC is driving (`out`) and whether it drives at all (`oe_n`, +1 meaning input/Hi-Z) — rather than the per-group data and direction words. + ## `gpio scan` It prints a baseline table of every GPIO pad, then polls in a loop and reports @@ -110,6 +118,11 @@ to reporting every output currently sitting low, which is a much broader guess. So a short list means the first rule fired and is worth trusting; a long one means the second did. +On SigmaStar the two rules survive with pads in place of groups: when the +streamer maps the GPIO block, a board whose whole pad list holds only one or +two driving pads gets exactly those, and without a streamer every pad +currently driving low is reported. + IR-cut is nearly always a *pair* of pads, driven in opposite directions to flip the filter between its two positions. To confirm a pair, run `gpio scan` and switch the camera between day and night mode in the web UI. Both pads change in diff --git a/src/hal/sstar_gpio.c b/src/hal/sstar_gpio.c new file mode 100644 index 00000000..8cbd62e5 --- /dev/null +++ b/src/hal/sstar_gpio.c @@ -0,0 +1,86 @@ +/* SigmaStar per-pad GPIO registers. + * + * Where HiSilicon banks eight pins behind one data and one direction word, + * SigmaStar's Infinity6C gives every pad its own one-byte register: bit 0 is + * the pin level, bit 1 the output value, and bit 2 the output enable, which + * on this family reads 1 while the pad drives Hi-Z (an input) and 0 while it + * drives. Like the pad-mux ports these registers are 16-bit slots in + * four-byte RIU holes, so they are reached with the 16-bit accessors and a + * 32-bit store would write two bytes that do not exist. + * + * The table below is the one the vendor's own driver carries + * (drivers/sstar/gpio/infinity6c/mhal_gpio.c in the SigmaStar kernel, GPL-2) + * converted from RIU byte offset r to physical 0x1F207C00 + 2 * r, and the + * result was measured against a live Infinity6C board: writing pads 12 and 30 + * through sysfs moves their bytes at exactly these addresses, and the idle + * levels of pads 10, 23 and 40/41 read back the states their exporters had + * left. The four PAD_ETH_* pads the vendor adds by hand sit outside the + * gpiochip range and are not listed. */ + +#include "hal/sstar_gpio.h" + +#include + +#include "chipid.h" +#include "hal/sstar.h" +#include "tools.h" + +static const uint32_t i6c_gpio_regs[] = { + 0x1F207C00, 0x1F207C04, 0x1F207C08, 0x1F207C0C, 0x1F207C10, 0x1F207C14, + 0x1F207C18, 0x1F207C1C, 0x1F207C20, 0x1F207C24, 0x1F207C28, 0x1F207C2C, + 0x1F207C30, 0x1F207C34, 0x1F207C38, 0x1F207C3C, 0x1F207C40, 0x1F207C44, + 0x1F207C48, 0x1F207C4C, 0x1F207C50, 0x1F207C54, 0x1F207C58, 0x1F207C5C, + 0x1F207C64, 0x1F207C68, 0x1F207C6C, 0x1F207C70, 0x1F207C74, 0x1F207C78, + 0x1F207C7C, 0x1F207C80, 0x1F207C84, 0x1F207C88, 0x1F207C8C, 0x1F207C90, + 0x1F207C94, 0x1F207C98, 0x1F207C9C, 0x1F207CA0, 0x1F207CA4, 0x1F207CA8, + 0x1F207CC4, 0x1F207CC8, 0x1F207CCC, 0x1F207CD0, 0x1F207CD4, 0x1F207CD8, + 0x1F207CDC, 0x1F207CE0, 0x1F207CE4, 0x1F207CE8, 0x1F207CEC, 0x1F207CF0, + 0x1F207CF4, 0x1F207CF8, 0x1F207CFC, 0x1F207D00, 0x1F207D04, 0x1F207D08, + 0x1F207D0C, 0x1F207D10, 0x1F207D14, 0x1F207D18, 0x1F207D1C, 0x1F207D20, + 0x1F207D24, 0x1F207D28, 0x1F207D2C, 0x1F207D30, 0x1F207D34, 0x1F207D38, + 0x1F207D3C, 0x1F207D40, 0x1F207D44, 0x1F207D48, 0x1F207D4C, 0x1F207D50, + 0x1F207D54, 0x1F207D58, 0x1F207D5C, 0x1F207D60, +}; + +bool sstar_gpio_supported(void) { + switch (chip_generation) { + case INFINITY6C: + return true; + default: + return false; + } +} + +int sstar_gpio_num_pads(void) { + if (!sstar_gpio_supported()) + return 0; + return (int)(sizeof(i6c_gpio_regs) / sizeof(i6c_gpio_regs[0])); +} + +uint32_t sstar_gpio_pad_addr(int pad) { + if (pad < 0 || pad >= sstar_gpio_num_pads()) + return 0; + return i6c_gpio_regs[pad]; +} + +bool sstar_gpio_read(int pad, uint8_t *val) { + uint32_t addr = sstar_gpio_pad_addr(pad); + if (!addr) + return false; + uint32_t data; + if (!mem_reg(addr, &data, OP_READ_16)) + return false; + *val = (uint8_t)(data & 0xff); + return true; +} + +bool sstar_gpio_write(int pad, uint8_t val) { + uint32_t addr = sstar_gpio_pad_addr(pad); + if (!addr) + return false; + uint32_t data; + if (!mem_reg(addr, &data, OP_READ_16)) + return false; + data = (data & 0xff00) | val; + return mem_reg(addr, &data, OP_WRITE_16); +} diff --git a/src/hal/sstar_gpio.h b/src/hal/sstar_gpio.h new file mode 100644 index 00000000..e655a254 --- /dev/null +++ b/src/hal/sstar_gpio.h @@ -0,0 +1,26 @@ +#ifndef HAL_SSTAR_GPIO_H +#define HAL_SSTAR_GPIO_H + +#include +#include + +/* Bit layout of the per-pad register, as the vendor driver uses it. */ +#define SSTAR_GPIO_BIT_IN 0x01 +#define SSTAR_GPIO_BIT_OUT 0x02 +#define SSTAR_GPIO_BIT_INPUT 0x04 + +bool sstar_gpio_supported(void); +int sstar_gpio_num_pads(void); + +/* Physical address of the pad's register, or 0 when there is no table for the + * current chip family or the pad is out of range. */ +uint32_t sstar_gpio_pad_addr(int pad); + +/* One pad's register through /dev/mem. Reads take the low byte of the 16-bit + * slot; writes read-modify-write it and leave the upper byte alone, because a + * wider store reaches RIU bytes that are not mapped. Serialise the callers: + * mem_reg() is single-threaded by contract. */ +bool sstar_gpio_read(int pad, uint8_t *val); +bool sstar_gpio_write(int pad, uint8_t val); + +#endif /* HAL_SSTAR_GPIO_H */ diff --git a/src/reginfo.c b/src/reginfo.c index ee241023..c7a6b656 100644 --- a/src/reginfo.c +++ b/src/reginfo.c @@ -3,6 +3,7 @@ #include "hal/hisi/hal_hisi.h" #include "hal/ingenic.h" #include "hal/sstar.h" +#include "hal/sstar_gpio.h" #include "padmux.h" #include "tools.h" @@ -3147,6 +3148,9 @@ const padmux_ops_t PADMUX_OPS_HISI = { static int gpio_mux_by(const char *gpio_number, int func_num, const char *set_func); static int padmux_refuse(int code, const char *pad_spec, const char *func); +static int sstar_gpio_getset(char **argv, bool set_op); +static int sstar_gpio_scan_cmd(void); +static char *sstar_gpio_possible_ircut(char *outbuf, size_t outlen); static void show_function(const uint16_t *func, unsigned val) { for (size_t i = 0; func[i]; i++) @@ -3405,6 +3409,8 @@ static int gpio_manipulate(char **argv, bool set_op) { size_t GPIO_Offset = 0; getchipname(); + if (sstar_gpio_supported()) + return sstar_gpio_getset(argv, set_op); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) { fprintf(stderr, "Chip is not supported\n"); return EXIT_FAILURE; @@ -3687,6 +3693,8 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { size_t GPIO_Offset = 0; *outbuf = 0; + if (sstar_gpio_supported()) + return sstar_gpio_possible_ircut(outbuf, outlen); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) return NULL; @@ -3757,6 +3765,205 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { return NULL; } +/* Whether a vendor daemon holds this SigmaStar's pad registers through a + * raw /dev/mem mapping of its own -- the same question the HiSilicon walk + * asks of its bank words. The pad registers of one chip share a single page, + * so the answer is one bit for every pad rather than a per-group mask. */ +static bool sstar_gpio_streamer_mapped(void) { + uint32_t base = sstar_gpio_pad_addr(0) & ~0xfffu; + uint32_t end = sstar_gpio_pad_addr(sstar_gpio_num_pads() - 1) + 4; + + DIR *proc = opendir("/proc"); + if (!proc) + return false; + + struct dirent *ent; + while ((ent = readdir(proc))) { + if (ent->d_name[0] < '2' || ent->d_name[0] > '9') + continue; + + char path[64]; + snprintf(path, sizeof(path), "/proc/%s/maps", ent->d_name); + FILE *f = fopen(path, "r"); + if (!f) + continue; + + char line[256]; + while (fgets(line, sizeof(line), f)) { + if (!strstr(line, "/dev/mem")) + continue; + unsigned long offset; + if (sscanf(line, "%*x-%*x %*s %lx", &offset) != 1) + continue; + if (offset >= base && offset < end) { + fclose(f); + closedir(proc); + return true; + } + } + fclose(f); + } + closedir(proc); + return false; +} + +/* `gpio get/set ` on SigmaStar: the pad number is a pad, its register is + * one byte, and there are no bank words and no group_pin syntax. The mux + * line is printed here, matching where the HiSilicon path prints it. */ +static int sstar_gpio_getset(char **argv, bool set_op) { + const char *gpio_num = argv[1]; + + gpio_mux_by(gpio_num, -1, NULL); + + char *tail; + long pad = strtol(gpio_num, &tail, 10); + if (tail == gpio_num || *tail || pad < 0 || pad >= sstar_gpio_num_pads()) { + fprintf(stderr, "GPIO %s is out of range\n", gpio_num); + return EXIT_FAILURE; + } + + uint8_t val; + if (!sstar_gpio_read((int)pad, &val)) { + fprintf(stderr, "read reg %#x error\n", sstar_gpio_pad_addr((int)pad)); + return EXIT_FAILURE; + } + + if (set_op) { + unsigned level = strtoul(argv[2], NULL, 10); + if (level > 1) + return EXIT_FAILURE; + + if (val & SSTAR_GPIO_BIT_INPUT) + val &= (uint8_t)~SSTAR_GPIO_BIT_INPUT; + if (level) + val |= SSTAR_GPIO_BIT_OUT; + else + val &= (uint8_t)~SSTAR_GPIO_BIT_OUT; + if (!sstar_gpio_write((int)pad, val)) { + fprintf(stderr, "write reg %#x error\n", + sstar_gpio_pad_addr((int)pad)); + return EXIT_FAILURE; + } + } else { + bool is_input = val & SSTAR_GPIO_BIT_INPUT; + printf("%d\n", is_input ? !!(val & SSTAR_GPIO_BIT_IN) + : !!(val & SSTAR_GPIO_BIT_OUT)); + } + + return EXIT_SUCCESS; +} + +/* The IR-cut question on SigmaStar: the HiSilicon heuristic walks bank words + * and keeps the output pins of a lightly-loaded, streamer-held group; the + * same idea over pads keeps the output pads, pared back the same two ways -- + * to the whole block when a daemon holds it and drives at most two pins, and + * otherwise to the outputs standing at zero. */ +static char *sstar_gpio_possible_ircut(char *outbuf, size_t outlen) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads || ipchw_padmux_by_prefix("", NULL, 0) < 0) + return NULL; + + uint8_t levels[nr_pads]; + bool is_gpio[nr_pads]; + memset(levels, 0, sizeof(levels)); + memset(is_gpio, 0, sizeof(is_gpio)); + + int output_count = 0; + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + return NULL; + } + is_gpio[pad] = true; + levels[pad] = val; + if (!(val & SSTAR_GPIO_BIT_INPUT)) + output_count++; + } + + bool streamer = sstar_gpio_streamer_mapped(); + char *ptr = outbuf; + + for (int pad = 0; pad < nr_pads; pad++) { + if (!is_gpio[pad]) + continue; + uint8_t val = levels[pad]; + if (val & SSTAR_GPIO_BIT_INPUT) + continue; + if (streamer && output_count > 2) + continue; + if (!streamer && (val & SSTAR_GPIO_BIT_OUT)) + continue; + + int nlen = snprintf(ptr, outlen, ",%d", pad); + outlen -= nlen; + ptr += nlen; + } + + if (strlen(outbuf) > 0) + return outbuf + 1; + return NULL; +} + +/* `gpio scan` on SigmaStar: watch every pad muxed to plain GPIO. The HiSilicon + * loop prints eight pins of a bank at a time; this one prints one line per + * pad, and only the three bits a pad register has. */ +static int sstar_gpio_scan_cmd(void) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads || ipchw_padmux_by_prefix("", NULL, 0) < 0) { + fprintf(stderr, "Platform is not supported\n"); + return EXIT_FAILURE; + } + + uint8_t state[nr_pads]; + memset(state, 0, sizeof(state)); + + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + return EXIT_FAILURE; + } + state[pad] = val; + printf("Pad:%2d, Addr:0x%08x, Data:0x%02X = in:%d out:%d oe_n:%d\n", + pad, sstar_gpio_pad_addr(pad), val, val & 1, (val >> 1) & 1, + (val >> 2) & 1); + } + + print_line(86); + printf("Waiting for while something changes...\n"); + while (1) { + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + break; + } + if (val == state[pad]) + continue; + + bool is_input = val & SSTAR_GPIO_BIT_INPUT; + printf("Pad:%d, Addr:0x%08x, 0x%02X --> 0x%02X = in:%d out:%d " + "oe_n:%d, Dir:%s, Level:%d\n", + pad, sstar_gpio_pad_addr(pad), state[pad], val, val & 1, + (val >> 1) & 1, (val >> 2) & 1, + is_input ? "Input" : "Output", + is_input ? (val & SSTAR_GPIO_BIT_IN) != 0 + : (val & SSTAR_GPIO_BIT_OUT) != 0); + state[pad] = val; + } + usleep(100000); + } + + return EXIT_SUCCESS; +} + static int gpio_scan_cmd() { int GPIO_Groups = 0; size_t GPIO_Base = 0; @@ -3769,6 +3976,8 @@ static int gpio_scan_cmd() { setvbuf(stdout, NULL, _IOLBF, 0); getchipname(); + if (sstar_gpio_supported()) + return sstar_gpio_scan_cmd(); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) return EXIT_FAILURE; diff --git a/src/reginfo_test.c b/src/reginfo_test.c index fb1f0196..daa27cce 100644 --- a/src/reginfo_test.c +++ b/src/reginfo_test.c @@ -13,6 +13,7 @@ #include "hal/hisi/hal_hisi.h" #include "hal/ingenic.h" #include "hal/sstar.h" +#include "hal/sstar_gpio.h" #include "ipchw.h" #include "padmux.h" @@ -1056,6 +1057,42 @@ static void test_table_integrity(void) { } } +/* The per-pad register addresses of the Infinity6C GPIO block, as measured on + * a live board: pads 12 and 30 move their bytes at exactly these addresses + * when written through sysfs, and the idle levels of 10, 23 and 40/41 read + * back what their exporters left. This pins the table an address edit would + * silently move. */ +#ifdef IPCHW_VENDOR_SSTAR +static void test_sstar_gpio_regs(void) { + puts("SigmaStar: per-pad GPIO registers (Infinity6C)"); + as_chip(INFINITY6C, "SSC37X"); + + CHECK(sstar_gpio_supported()); + CHECK(sstar_gpio_num_pads() == 82); + CHECK(sstar_gpio_pad_addr(0) == 0x1F207C00); + CHECK(sstar_gpio_pad_addr(12) == 0x1F207C30); + CHECK(sstar_gpio_pad_addr(23) == 0x1F207C5C); + CHECK(sstar_gpio_pad_addr(30) == 0x1F207C7C); + CHECK(sstar_gpio_pad_addr(41) == 0x1F207CA8); + CHECK(sstar_gpio_pad_addr(42) == 0x1F207CC4); + CHECK(sstar_gpio_pad_addr(81) == 0x1F207D60); + + uint32_t prev = 0; + for (int pad = 0; pad < 82; pad++) { + uint32_t addr = sstar_gpio_pad_addr(pad); + CHECK(addr >= 0x1F207C00 && addr <= 0x1F207D60); + CHECK((addr & 3) == 0); + CHECK(addr > prev); + prev = addr; + } + + as_chip(0, "none"); + CHECK(!sstar_gpio_supported()); + CHECK(sstar_gpio_num_pads() == 0); + CHECK(sstar_gpio_pad_addr(31) == 0); +} +#endif + int main(void) { /* Every register the tests below touch is one of these, not a camera's. */ padmux_set_io(&FAKE_IO); @@ -1080,6 +1117,9 @@ int main(void) { #ifdef IPCHW_PADMUX_SSTAR test_sstar(); #endif +#ifdef IPCHW_VENDOR_SSTAR + test_sstar_gpio_regs(); +#endif #ifdef IPCHW_PADMUX_INGENIC test_ingenic(); #endif From 2566ebb362d92b04e20d53ac0982189375369516 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Fri, 25 Sep 2026 17:32:05 +0300 Subject: [PATCH 2/3] sstar: review round -- levels, windows, and a scan that stops Three of the four review findings had a HiSilicon twin, because the SigmaStar paths were written against the HiSilicon ones, and are fixed in the one place both share: - The level a `gpio set` writes is parsed strictly now. strtoul reads every string that is not a number as 0, and 0 is a level the command really writes, so `gpio set 12 foo` used to switch the pad to an output and drive it low. Both vendors' set paths refused after this fix only. - A /dev/mem mapping counts from its full interval, not only from the page its offset names: a daemon holding one broad RIU window from below held the GPIO registers all the same, and the walk now knows. The interval math is gpio_windows_in_mapping(), pinned by tests; both the HiSilicon group walk and the SigmaStar single-window question go through it. - The /proc walk visits every digit-named pid, including init. PID 1 running the streamer is a real shape. - `gpio scan` on either vendor now exits at the first register read that fails, instead of retrying every 100 ms and logging the same error forever while watching nothing. Verified on the SSC37X board: `gpio set 12 foo` refuses with the pad byte at 0x1f207c30 untouched (0x58 before and after), valid sets still move it 0x58/0x5b, and the board report's possible-IR-cut-GPIO is the same eleven pads as before the walk was fixed. The HiSilicon twins are not live-verified this round -- the lab HiSilicon cameras did not answer. --- src/reginfo.c | 122 +++++++++++++++++++++++++-------------------- src/reginfo.h | 16 ++++++ src/reginfo_test.c | 46 +++++++++++++++++ 3 files changed, 130 insertions(+), 54 deletions(-) diff --git a/src/reginfo.c b/src/reginfo.c index c7a6b656..2e14ffd4 100644 --- a/src/reginfo.c +++ b/src/reginfo.c @@ -9,8 +9,9 @@ #include "ipchw.h" -#include #include +#include +#include #include #include #include @@ -3133,6 +3134,28 @@ const padmux_ops_t PADMUX_OPS_HISI = { .set = hisi_set, }; +bool parse_gpio_level(const char *arg, unsigned *level) { + char *tail; + errno = 0; + unsigned long val = strtoul(arg, &tail, 10); + if (errno || tail == arg || *tail || val > 1) + return false; + *level = (unsigned)val; + return true; +} + +uint32_t gpio_windows_in_mapping(unsigned long off, unsigned long len, + uint32_t base, uint32_t stride, int nwin) { + uint32_t mask = 0; + for (int i = 0; i < nwin && i < 32; i++) { + uint32_t lo = base + (uint32_t)i * stride; + uint32_t hi = lo + stride; + if (off < hi && off + len > lo) + mask |= 1u << i; + } + return mask; +} + /* Everything below is the command-line half of ipctool: it prints, it parses * argv, and it calls print_usage() out of main.c. STANDALONE_LIBRARY builds * (libipchw) take the tables and the lookups above and stop here. */ @@ -3432,9 +3455,13 @@ static int gpio_manipulate(char **argv, bool set_op) { uint32_t val, address = GPIO_Base + (group * GPIO_Offset) + mask; if (set_op) { - unsigned val = strtoul(argv[2], NULL, 10); - if (val > 1) + /* Refused before any register is touched: the old parse made + * `gpio set 12 foo` into a real drive-low. */ + unsigned level; + if (!parse_gpio_level(argv[2], &level)) { + fprintf(stderr, "Level '%s' is not 0 or 1\n", argv[2]); return EXIT_FAILURE; + } size_t daddress = GPIO_Base + (group * GPIO_Offset) + 0x400; uint32_t direct; @@ -3451,7 +3478,7 @@ static int gpio_manipulate(char **argv, bool set_op) { } } - uint32_t cmd = val << num; + uint32_t cmd = level << num; if (!mem_reg(address, &cmd, OP_WRITE)) { printf("write reg %#x error\n", address); return EXIT_FAILURE; @@ -3650,17 +3677,28 @@ static bool fill_enabled_gpios(size_t *enabled, size_t GPIO_Groups) { return true; } -static uint32_t find_streamer_gpio_groups(size_t GPIO_Base, size_t GPIO_Offset, - int GPIO_Groups) { - uint32_t mapped = 0; +/* A GPIO block's registers, found through a daemon's own /dev/mem mappings. + * + * One walk answers both vendors' question: HiSilicon asks which of its + * group-sized windows are visible and gets a mask; SigmaStar asks whether the + * single window its pads share is visible and reads bit 0. Any process whose + * /proc name is all digits is visited -- including init, which on a minimal + * firmware is the streamer. A mapping counts from its full interval: the + * file offset is its physical address and the vaddr span is its length, so a + * daemon holding one broad RIU window from well below is mapped here the + * same as one that mapped the registers exactly. */ +static uint32_t dev_mem_windows_marked(uint32_t base, uint32_t stride, + int nwin) { + uint32_t marked = 0; DIR *proc = opendir("/proc"); if (!proc) return 0; - size_t GPIO_End = GPIO_Base + GPIO_Groups * GPIO_Offset; struct dirent *ent; while ((ent = readdir(proc))) { - if (ent->d_name[0] < '2' || ent->d_name[0] > '9') + /* Every digit-only name is a pid entry; "." and ".." and the + * non-numeric /proc files fail this test on their first character. */ + if (ent->d_name[strspn(ent->d_name, "0123456789")]) continue; char path[64]; @@ -3670,21 +3708,19 @@ static uint32_t find_streamer_gpio_groups(size_t GPIO_Base, size_t GPIO_Offset, continue; char line[256]; + unsigned long start, end, off; while (fgets(line, sizeof(line), f)) { if (!strstr(line, "/dev/mem")) continue; - unsigned long offset; - if (sscanf(line, "%*x-%*x %*s %lx", &offset) != 1) + if (sscanf(line, "%lx-%lx %*s %lx", &start, &end, &off) != 3) continue; - if (offset >= GPIO_Base && offset < GPIO_End) { - int group = (offset - GPIO_Base) / GPIO_Offset; - mapped |= (1u << group); - } + marked |= + gpio_windows_in_mapping(off, end - start, base, stride, nwin); } fclose(f); } closedir(proc); - return mapped; + return marked; } char *gpio_possible_ircut(char *outbuf, size_t outlen) { @@ -3699,7 +3735,7 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { return NULL; uint32_t streamer_groups = - find_streamer_gpio_groups(GPIO_Base, GPIO_Offset, GPIO_Groups); + dev_mem_windows_marked(GPIO_Base, GPIO_Offset, GPIO_Groups); size_t enabled[GPIO_Groups]; if (!fill_enabled_gpios(enabled, GPIO_Groups)) @@ -3772,39 +3808,7 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { static bool sstar_gpio_streamer_mapped(void) { uint32_t base = sstar_gpio_pad_addr(0) & ~0xfffu; uint32_t end = sstar_gpio_pad_addr(sstar_gpio_num_pads() - 1) + 4; - - DIR *proc = opendir("/proc"); - if (!proc) - return false; - - struct dirent *ent; - while ((ent = readdir(proc))) { - if (ent->d_name[0] < '2' || ent->d_name[0] > '9') - continue; - - char path[64]; - snprintf(path, sizeof(path), "/proc/%s/maps", ent->d_name); - FILE *f = fopen(path, "r"); - if (!f) - continue; - - char line[256]; - while (fgets(line, sizeof(line), f)) { - if (!strstr(line, "/dev/mem")) - continue; - unsigned long offset; - if (sscanf(line, "%*x-%*x %*s %lx", &offset) != 1) - continue; - if (offset >= base && offset < end) { - fclose(f); - closedir(proc); - return true; - } - } - fclose(f); - } - closedir(proc); - return false; + return dev_mem_windows_marked(base, end - base, 1) != 0; } /* `gpio get/set ` on SigmaStar: the pad number is a pad, its register is @@ -3829,9 +3833,13 @@ static int sstar_gpio_getset(char **argv, bool set_op) { } if (set_op) { - unsigned level = strtoul(argv[2], NULL, 10); - if (level > 1) + /* Refused before the register changes: the old parse made + * `gpio set 12 foo` into a real drive-low. */ + unsigned level; + if (!parse_gpio_level(argv[2], &level)) { + fprintf(stderr, "Level '%s' is not 0 or 1\n", argv[2]); return EXIT_FAILURE; + } if (val & SSTAR_GPIO_BIT_INPUT) val &= (uint8_t)~SSTAR_GPIO_BIT_INPUT; @@ -3942,8 +3950,11 @@ static int sstar_gpio_scan_cmd(void) { continue; uint8_t val; if (!sstar_gpio_read(pad, &val)) { + /* A /dev/mem that has stopped reading will not start again; + * a blind watcher that retries every 100 ms and names the + * same pad each time is worse than stopping. */ fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); - break; + return EXIT_FAILURE; } if (val == state[pad]) continue; @@ -4018,8 +4029,11 @@ static int gpio_scan_cmd() { size_t address = GPIO_Base + (group * GPIO_Offset) + mask; uint32_t value; if (!mem_reg(address, &value, OP_READ)) { + /* A /dev/mem that has stopped reading will not start again; + * a blind watcher that retries every 100 ms and names the + * same register each time is worse than stopping. */ fprintf(stderr, "Error at %#zx\n", address); - break; + return EXIT_FAILURE; } if (state[group] != value) { bool HeaderByte = false; diff --git a/src/reginfo.h b/src/reginfo.h index 5c5abe3c..cae62fc1 100644 --- a/src/reginfo.h +++ b/src/reginfo.h @@ -1,6 +1,7 @@ #ifndef REGINFO_H #define REGINFO_H +#include #include #include @@ -31,4 +32,19 @@ int reginfo_cmd(int argc, char **argv); int gpio_cmd(int argc, char **argv); char *gpio_possible_ircut(char *outbuf, size_t outlen); +/* The level a `gpio set` may write, parsed strictly. strtoul answers 0 for + * every string that is not a number, and 0 is a level the command really + * writes -- it switches the pad to an output and drives it low -- so a bare + * strtoul turned every typo into hardware. The number must end the string + * and be 0 or 1. */ +bool parse_gpio_level(const char *arg, unsigned *level); + +/* Which of nwin equal windows, the first at base and each stride wide, one + * /dev/mem mapping covers. A /dev/mem mapping's file offset is its physical + * address and its length is the vaddr span it names, so the interval under + * test is [off, off + len): a daemon mapping one broad window from well + * below is holding the windows the same as one mapped exactly. */ +uint32_t gpio_windows_in_mapping(unsigned long off, unsigned long len, + uint32_t base, uint32_t stride, int nwin); + #endif /* REGINFO_H */ diff --git a/src/reginfo_test.c b/src/reginfo_test.c index daa27cce..7a2a7957 100644 --- a/src/reginfo_test.c +++ b/src/reginfo_test.c @@ -16,6 +16,7 @@ #include "hal/sstar_gpio.h" #include "ipchw.h" #include "padmux.h" +#include "reginfo.h" static int failures; @@ -1093,6 +1094,49 @@ static void test_sstar_gpio_regs(void) { } #endif +/* parse_gpio_level: 0 is a level the command really writes, so the string + * must be exactly the level, and every string strtoul reads as 0 is a + * refusal. The old bare strtoul made `gpio set 12 foo` drive the pad low. */ +static void test_gpio_level_parse(void) { + puts("gpio set: a level is the string, and only 0 or 1 is a level"); + unsigned level; + + CHECK(parse_gpio_level("0", &level) && level == 0); + CHECK(parse_gpio_level("1", &level) && level == 1); + CHECK(parse_gpio_level("01", &level) && level == 1); + + CHECK(!parse_gpio_level("", &level)); + CHECK(!parse_gpio_level("foo", &level)); + CHECK(!parse_gpio_level("1x", &level)); + CHECK(!parse_gpio_level("0x1", &level)); + CHECK(!parse_gpio_level("2", &level)); + CHECK(!parse_gpio_level("-1", &level)); + CHECK(!parse_gpio_level("18446744073709551616", &level)); +} + +/* gpio_windows_in_mapping: a mapping covers what its length covers, not + * only what its start address falls inside -- a daemon holding one broad + * window from below the registers is holding them the same as one that + * mapped the exact page. */ +static void test_gpio_windows_in_mapping(void) { + puts("gpio reports: a mapping covers what its length covers"); + uint32_t base = 0x1000, stride = 0x100; + + CHECK(gpio_windows_in_mapping(0x1000, 0x100, base, stride, 3) == 0x1); + CHECK(gpio_windows_in_mapping(0x0, 0x1200, base, stride, 3) == 0x3); + CHECK(gpio_windows_in_mapping(0x1100, 0x200, base, stride, 3) == 0x6); + CHECK(gpio_windows_in_mapping(0x1300, 0x100, base, stride, 3) == 0x0); + CHECK(gpio_windows_in_mapping(0x1250, 0x20, base, stride, 3) == 0x4); + + /* The SigmaStar call: one window, not page-aligned at either end. */ + CHECK(gpio_windows_in_mapping(0x1F000000, 0x400000, 0x1F207C00, 0x1D64, + 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F207000, 0x1000, 0x1F207C00, 0x1D64, 1) == + 0x1); + CHECK(gpio_windows_in_mapping(0x1F20A000, 0x1000, 0x1F207C00, 0x1D64, 1) == + 0x0); +} + int main(void) { /* Every register the tests below touch is one of these, not a camera's. */ padmux_set_io(&FAKE_IO); @@ -1120,6 +1164,8 @@ int main(void) { #ifdef IPCHW_VENDOR_SSTAR test_sstar_gpio_regs(); #endif + test_gpio_level_parse(); + test_gpio_windows_in_mapping(); #ifdef IPCHW_PADMUX_INGENIC test_ingenic(); #endif From 8d95ba979fe0f27d56ccf5d5c4d05b5bd645192f Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 25 Sep 2026 17:54:28 +0000 Subject: [PATCH 3/3] gpio: the /proc walk must not find ipctool's own /dev/mem window mem_reg() keeps its last /dev/mem window mapped, and the SigmaStar IR-cut hint reads every pad before asking the walk whether a daemon holds the pads. The walk visited every digit-named pid, ipctool's own included, so it always found the window it had just used, always answered "streamer", and on any board with more than two driving pads the hint was dropped. The fallback rule could never run. Skip getpid(). The window the hint asks about now comes from sstar_gpio_window(), and reginfo_test checks that call -- 0x1F207000 + 0xD64 -- instead of a stand-in with a different base and length. --- src/hal/sstar_gpio.c | 9 +++++++++ src/hal/sstar_gpio.h | 5 +++++ src/reginfo.c | 15 ++++++++++++--- src/reginfo_test.c | 21 +++++++++++++-------- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/hal/sstar_gpio.c b/src/hal/sstar_gpio.c index 8cbd62e5..1d6c1b37 100644 --- a/src/hal/sstar_gpio.c +++ b/src/hal/sstar_gpio.c @@ -63,6 +63,15 @@ uint32_t sstar_gpio_pad_addr(int pad) { return i6c_gpio_regs[pad]; } +bool sstar_gpio_window(uint32_t *base, uint32_t *len) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads) + return false; + *base = sstar_gpio_pad_addr(0) & ~0xfffu; + *len = sstar_gpio_pad_addr(nr_pads - 1) + 4 - *base; + return true; +} + bool sstar_gpio_read(int pad, uint8_t *val) { uint32_t addr = sstar_gpio_pad_addr(pad); if (!addr) diff --git a/src/hal/sstar_gpio.h b/src/hal/sstar_gpio.h index e655a254..86cf5160 100644 --- a/src/hal/sstar_gpio.h +++ b/src/hal/sstar_gpio.h @@ -16,6 +16,11 @@ int sstar_gpio_num_pads(void); * current chip family or the pad is out of range. */ uint32_t sstar_gpio_pad_addr(int pad); +/* The one window every pad register of the chip sits in, from the page the + * first pad lives on to the end of the last pad's slot: what a daemon has to + * have mapped to be holding the pads. False when there is no table. */ +bool sstar_gpio_window(uint32_t *base, uint32_t *len); + /* One pad's register through /dev/mem. Reads take the low byte of the 16-bit * slot; writes read-modify-write it and leave the upper byte alone, because a * wider store reaches RIU bytes that are not mapped. Serialise the callers: diff --git a/src/reginfo.c b/src/reginfo.c index 2e14ffd4..a76ce958 100644 --- a/src/reginfo.c +++ b/src/reginfo.c @@ -3694,12 +3694,20 @@ static uint32_t dev_mem_windows_marked(uint32_t base, uint32_t stride, if (!proc) return 0; + char self[16]; + snprintf(self, sizeof(self), "%d", (int)getpid()); + struct dirent *ent; while ((ent = readdir(proc))) { /* Every digit-only name is a pid entry; "." and ".." and the * non-numeric /proc files fail this test on their first character. */ if (ent->d_name[strspn(ent->d_name, "0123456789")]) continue; + /* Not ourselves: mem_reg() keeps its last /dev/mem window mapped, so + * after any register read ipctool holds the very window it is asking + * about and would always find a "streamer" -- itself. */ + if (!strcmp(ent->d_name, self)) + continue; char path[64]; snprintf(path, sizeof(path), "/proc/%s/maps", ent->d_name); @@ -3806,9 +3814,10 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { * asks of its bank words. The pad registers of one chip share a single page, * so the answer is one bit for every pad rather than a per-group mask. */ static bool sstar_gpio_streamer_mapped(void) { - uint32_t base = sstar_gpio_pad_addr(0) & ~0xfffu; - uint32_t end = sstar_gpio_pad_addr(sstar_gpio_num_pads() - 1) + 4; - return dev_mem_windows_marked(base, end - base, 1) != 0; + uint32_t base, len; + if (!sstar_gpio_window(&base, &len)) + return false; + return dev_mem_windows_marked(base, len, 1) != 0; } /* `gpio get/set ` on SigmaStar: the pad number is a pad, its register is diff --git a/src/reginfo_test.c b/src/reginfo_test.c index 7a2a7957..bfd37520 100644 --- a/src/reginfo_test.c +++ b/src/reginfo_test.c @@ -1087,10 +1087,23 @@ static void test_sstar_gpio_regs(void) { prev = addr; } + /* The window the IR-cut hint asks the /proc walk about -- the call + * itself, not a stand-in for it: one page-aligned start, an end that is + * not. */ + uint32_t base, len; + CHECK(sstar_gpio_window(&base, &len)); + CHECK(base == 0x1F207000 && len == 0xD64); + CHECK(gpio_windows_in_mapping(0x1F000000, 0x400000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F200000, 0x10000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F207000, 0x1000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F206000, 0x1000, base, len, 1) == 0x0); + CHECK(gpio_windows_in_mapping(0x1F208000, 0x1000, base, len, 1) == 0x0); + as_chip(0, "none"); CHECK(!sstar_gpio_supported()); CHECK(sstar_gpio_num_pads() == 0); CHECK(sstar_gpio_pad_addr(31) == 0); + CHECK(!sstar_gpio_window(&base, &len)); } #endif @@ -1127,14 +1140,6 @@ static void test_gpio_windows_in_mapping(void) { CHECK(gpio_windows_in_mapping(0x1100, 0x200, base, stride, 3) == 0x6); CHECK(gpio_windows_in_mapping(0x1300, 0x100, base, stride, 3) == 0x0); CHECK(gpio_windows_in_mapping(0x1250, 0x20, base, stride, 3) == 0x4); - - /* The SigmaStar call: one window, not page-aligned at either end. */ - CHECK(gpio_windows_in_mapping(0x1F000000, 0x400000, 0x1F207C00, 0x1D64, - 1) == 0x1); - CHECK(gpio_windows_in_mapping(0x1F207000, 0x1000, 0x1F207C00, 0x1D64, 1) == - 0x1); - CHECK(gpio_windows_in_mapping(0x1F20A000, 0x1000, 0x1F207C00, 0x1D64, 1) == - 0x0); } int main(void) {