Sample Header Ad - 728x90

Unix & Linux Stack Exchange

Q&A for users of Linux, FreeBSD and other Unix-like operating systems

Latest Questions

0 votes
1 answers
26 views
difference between "line-name" and "gpio-line-names" in the devicetree for gpio
I'm trying to understand why the devicetree property `line-name` (which can be used with `gpio-hog`) sets the `label` attribute where as `gpio-line-names` sets the `name` attribute for each gpio associated with the chip? And what is the rational is for the two similarly named properties affecting ve...
I'm trying to understand why the devicetree property line-name (which can be used with gpio-hog) sets the label attribute where as gpio-line-names sets the name attribute for each gpio associated with the chip? And what is the rational is for the two similarly named properties affecting very different attributes in the driver? I'm also curious wath is the intended conceptual difference between the label and the name? Background: I'm trying to use libgpiod from user-space to locate a named gpio from the devicetree but that only seems to work if the name attribute is set and it appears that I can only do that if I set the name using gpio-line-names in the chip node, but that's very cumbersome and error prone considering that I have to set a name for every line on the chip, many of which I probably don't care about. Is there some other way to set the gpio name attribute from the devicetree? *Bonus question: Why does libgpiod use the term consumer for the label attribute and what is the rational for using the term consumer?*
Matt Schuckmann (81 rep)
Jul 28, 2025, 11:23 PM • Last activity: Aug 1, 2025, 08:38 PM
1 votes
2 answers
3392 views
Cross-compile libgpiod library and use it in my program
I am trying to cross-compile a program with the "gpiod" library. My host pc has Ubuntu 18.04 and the target platform I am cross-compiling for is a Board with IMX8X SOM. I found source code at https://variwiki.com/index.php?title=MX8_GPIO&release=RELEASE_DUNFELL_V1.3_VAR-SOM-MX8X and I wanted to comp...
I am trying to cross-compile a program with the "gpiod" library. My host pc has Ubuntu 18.04 and the target platform I am cross-compiling for is a Board with IMX8X SOM. I found source code at https://variwiki.com/index.php?title=MX8_GPIO&release=RELEASE_DUNFELL_V1.3_VAR-SOM-MX8X and I wanted to compile it, following the paragraph "2.2 libgpiod C Application". The OS used in my board is the "dunfell" distro compiled with Yocto. The cross-compiler I am using is also created as sdk withing the Yocto environments (aarch64-fslc-linux-gcc). When I compiled, I firstly obtained "fatal error: gpiod.h: No such file or directory" So, this library is not included by default in the provided toolchain. So I tried to install the (cross)library in my pc, following the instructions at https://www.beyondlogic.org/an-introduction-to-chardev-gpio-and-libgpiod-on-the-raspberry-pi/ , but it did not work. The second error I get is /opt/fslc-wayland/3.1/sysroots/x86_64-fslcsdk-linux/usr/libexec/aarch64-fslc-linux/gcc/aarch64-fslc-linux/9.3.0/real-ld: cannot find -lgpiod collect2: error: ld returned 1 exit status These are my compilation flags: CFLAGS = -Wall -I$(INCLUDE) --sysroot=${SDKTARGETSYSROOT} -Og -lgpiod I am new to this kind of issues and I hope I am making trivial mistakes. Thanks in advance if someone is able to help!
powermignolo (11 rep)
Dec 29, 2021, 11:56 AM • Last activity: Jul 15, 2025, 08:07 AM
0 votes
0 answers
18 views
How to read GPIO interrupt timing at the application layer?
I use the a40i(arm) processor and read the gpio interrupt flag at the application layer. I read the gpio flag as a character device. The gpio interrupt is given externally and has a stable 4ms period. The kernel has an rt patch, and the interrupt will be threaded. The gpio interrupt has its priority...
I use the a40i(arm) processor and read the gpio interrupt flag at the application layer. I read the gpio flag as a character device. The gpio interrupt is given externally and has a stable 4ms period. The kernel has an rt patch, and the interrupt will be threaded. The gpio interrupt has its priority set by renice, and isolated core and core binding processing is also done. However, there will be time fluctuations at the application layer. For example, there is a certain possibility that reading two interrupts will take more than 6ms, but most of the time is still around 4.2ms. Is there any way to optimize this fluctuation? the driver code of GPIO: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int MajorDevNum = 0; static int SpiioNum = 0; static int IrqNum = 0; static struct class *SpiioClass; static int IrqOccurred = 0; static wait_queue_head_t WaitQueue; static spinlock_t RWLock; #define GPIO_PH20 244 static void SetIrqOccurred(int newIrqOccurred) { spin_lock_irq(&RWLock); IrqOccurred = newIrqOccurred; gpio_set_value(GPIO_PH20, IrqOccurred); spin_unlock_irq(&RWLock); } static irqreturn_t spiio_irq_handler(int irq, void *dev_id) { if (irq == IrqNum) { SetIrqOccurred(1); } return IRQ_HANDLED; } static int spiio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { int ret = IrqOccurred; if (ret) SetIrqOccurred(0); return ret; } /* write(fd, &val, 1); */ static int spiio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { SetIrqOccurred(0); return 1; } static int spiio_drv_open(struct inode *node, struct file *file) { int err = 0; pr_info("this is open for gpio\n"); return 0; } static int spiio_drv_close(struct inode *node, struct file *file) { return 0; } static long spiio_drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return 0; } static unsigned int spiio_drv_poll(struct file *file, struct poll_table *wait) { return 0; } static struct file_operations spiio_drv = { .owner = THIS_MODULE, .open = spiio_drv_open, .read = spiio_drv_read, .write = spiio_drv_write, .release = spiio_drv_close, .unlocked_ioctl = spiio_drv_ioctl, .poll = spiio_drv_poll, }; int spiio_probe(struct platform_device *pdev) { int err = 0; struct gpio_config pinConfig; char pinName = {0}; unsigned int config = 0; struct device_node *nd = pdev->dev.of_node; if (!nd) { pr_err("no found device tree node\n"); return -1; } if ((of_gpio_named_count(nd, "spiio_gpio") name) < 0) { pr_err("gpio request failed, GPIO:%d\n", SpiioNum); gpio_free(SpiioNum); return -1; } sunxi_gpio_to_name(pinConfig.gpio, pinName); pr_info("pinName = %s\n", pinName); pr_info("index = %d\n", pinConfig.gpio); pr_info("mul_sel = %d\n", pinConfig.mul_sel); pr_info("pull = %d\n", pinConfig.pull); pr_info("drv_level = %d\n", pinConfig.drv_level); pr_info("data = %d\n", pinConfig.data); config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, pinConfig.mul_sel); pin_config_set(SUNXI_PINCTRL, pinName, config); if (pinConfig.pull != GPIO_PULL_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, pinConfig.pull); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set pull:%d\n", pinConfig.pull); } if (pinConfig.drv_level != GPIO_DRVLVL_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, pinConfig.drv_level); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set drv_level:%d\n", pinConfig.drv_level); } if (pinConfig.data != GPIO_DATA_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, pinConfig.data); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set data:%d\n", pinConfig.data); } if (gpio_direction_input(SpiioNum) < 0) { pr_err("gpio_direction_input failed %d\n", SpiioNum); gpio_free(SpiioNum); return -1; } MajorDevNum = register_chrdev(0, "spiio", &spiio_drv); /* /dev/spiio */ if (MajorDevNum < 0) { pr_err("failed to get major device number\n"); gpio_free(SpiioNum); return -1; } SpiioClass = class_create(THIS_MODULE, "spiio_class"); if (IS_ERR(SpiioClass)) { pr_err("spiio class has an error\n"); unregister_chrdev(MajorDevNum, "spiio"); gpio_free(SpiioNum); return PTR_ERR(SpiioClass); } device_create(SpiioClass, NULL, MKDEV(MajorDevNum, 0), NULL, "spiio%d", 0); /* /dev/100ask_spiio0 */ pr_info("gpio is ok, spiio num is %d\n", SpiioNum); #if 1 spin_lock_init(&RWLock); init_waitqueue_head(&WaitQueue); IrqNum = gpio_to_irq(SpiioNum); if (IS_ERR(IrqNum)) { pr_err("irqNum is err\n"); free_irq(IrqNum, NULL); gpio_free(SpiioNum); unregister_chrdev(MajorDevNum, "spiio"); return -1; } if ((err = request_irq(IrqNum, spiio_irq_handler, IRQF_TRIGGER_RISING, "spiio_irq", NULL)) < 0) { pr_err("requst_irq failed, err = %d\n", err); free_irq(IrqNum, NULL); gpio_free(SpiioNum); unregister_chrdev(MajorDevNum, "spiio"); return -1; } pr_info("add irq %d on pin %d success.\n", IrqNum, SpiioNum); #endif #if 1 cpumask_t cpumask; cpumask_clear(&cpumask); cpumask_set_cpu(2, &cpumask); // 将中断绑定到CPU2 err = irq_set_affinity_hint(IrqNum, &cpumask); if (err == 0) { pr_info("set irq cpu to 2 successfully\n"); } else { pr_err("failed to set irq cpu to 2, err =%d\n", err); } #endif if (gpio_request(GPIO_PH20, "PH20_GPIO")) { pr_err("failed to request GPIO PH20\n"); } else { gpio_direction_output(GPIO_PH20, 1); pr_info("request GPIO PH20 successfully\n"); } return 0; } int spiio_remove(struct platform_device *pdev) { #if 1 free_irq(IrqNum, NULL); #endif device_destroy(SpiioClass, MKDEV(MajorDevNum, 0)); class_destroy(SpiioClass); unregister_chrdev(MajorDevNum, "spiio"); gpio_free(SpiioNum); pr_info("gpio remove ...\n"); return 0; } struct of_device_id ids[] = { {.compatible = "dobot, spiio"}, {}, }; static struct platform_driver chip_demo_gpio_driver = { .probe = spiio_probe, .remove = spiio_remove, /* .shutdown = spiio_remove,*/ .driver = { .name = "spiio", .of_match_table = ids, }, }; static int __init spiio_init(void) { int err; err = platform_driver_register(&chip_demo_gpio_driver); return err; } static void __exit spiio_exit(void) { platform_driver_unregister(&chip_demo_gpio_driver); } module_init(spiio_init); module_exit(spiio_exit); MODULE_LICENSE("GPL"); According to the test results, the entire ISR is executed with a delay. static void SetIrqOccurred(int newIrqOccurred) { spin_lock_irq(&RWLock); IrqOccurred = newIrqOccurred; gpio_set_value(GPIO_PH20, IrqOccurred); spin_unlock_irq(&RWLock); }
yanzhang.guo (113 rep)
Apr 24, 2025, 01:20 AM • Last activity: Apr 24, 2025, 02:02 AM
0 votes
0 answers
30 views
Bit banging PWM on Orange PI with Rockwell chip
I've read up on bit banging and I know how it works but I don't know how to implement it. I tried just using libraries written by other people in C or python but I'm using an orange pi 3B which uses a Rockchip processor. So, when I try to install, I get the `processor mismatch` error because they we...
I've read up on bit banging and I know how it works but I don't know how to implement it. I tried just using libraries written by other people in C or python but I'm using an orange pi 3B which uses a Rockchip processor. So, when I try to install, I get the processor mismatch error because they were written for Allwinner, sunxi or H5 processors. Pls how do I start. I considered something like echo 0 > /sys/class/pwm/pwmchip1/export sleep 0.05 echo 1 > /sys/class/pwm/pwmchip1/export fd I've tried evergreen-it-dev/orangepwm , nopnop2002/pwmlib-opi , vlna/orange-pi-pwm and the rest were documented to only work on specific processors.
izumi (1 rep)
Mar 7, 2025, 11:59 AM • Last activity: Mar 7, 2025, 03:26 PM
0 votes
0 answers
18 views
Arm linux interrupt binding failed
I am running the Linux 3.10 kernel on an ARM processor and writing a gpio driver. The system assigned interrupt number 182. I used the echo command to bind the core and wanted to bind the interrupt to CPU2, but it failed. cat /proc/irq/182/smp_affinity 4 cat /proc/irq/182/smp_affinity_list 2 However...
I am running the Linux 3.10 kernel on an ARM processor and writing a gpio driver. The system assigned interrupt number 182. I used the echo command to bind the core and wanted to bind the interrupt to CPU2, but it failed. cat /proc/irq/182/smp_affinity 4 cat /proc/irq/182/smp_affinity_list 2 However, I checked the following information and there was no problem. What is going on? My cpu2 and cpu3 are isolated. cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 29: 2000407 1993990 308737 1328118 GIC arch_timer 30: 0 0 0 0 GIC arch_timer 32: 0 0 0 0 GIC axp22_irq_chip 33: 410 0 0 0 GIC uart0 39: 1090 0 0 0 GIC twi0 40: 0 0 0 0 GIC twi1 41: 48 0 0 0 GIC twi2 43: 6211 0 115847 0 GIC spi1 54: 0 0 0 0 GIC sunxi_timer 59: 0 0 0 0 GIC 1c02000.dma-controller 60: 95854 0 31972 0 GIC PIN_GRP 66: 13154 0 0 0 GIC sunxi-mmc 71: 0 0 0 0 GIC ehci_hcd:usb1 72: 0 0 0 0 GIC ohci_hcd:usb4 76: 0 0 0 0 GIC dispaly 78: 0 0 0 0 GIC 1e80000.g2d 96: 0 0 0 0 GIC ohci_hcd:usb5 97: 0 0 0 0 GIC ohci_hcd:usb6 98: 0 0 0 0 GIC mdfs 108: 0 0 0 0 GIC ehci_hcd:usb2 110: 0 0 0 0 GIC ehci_hcd:usb3 117: 3069 0 0 0 GIC 1c50000.eth 120: 0 0 0 0 GIC twi3 121: 0 0 0 0 GIC twi4 125: 0 0 0 0 GIC DE-Interlace 128: 0 0 0 0 GIC 1000000.tr 182: 52594 75232 0 0 - spiio_irq IPI0: 0 0 0 0 CPU wakeup interrupts IPI1: 0 0 0 0 Timer broadcast interrupts IPI2: 460761 501962 125084 114712 Rescheduling interrupts IPI3: 2 2 4 4 Function call interrupts IPI4: 2 3 0 0 Single function call interrupts IPI5: 0 0 0 0 CPU stop interrupts IPI6: 0 0 0 0 completion interrupts IPI7: 0 0 0 0 CPU backtrace Err: 0
Vimer (67 rep)
Feb 25, 2025, 04:18 AM • Last activity: Feb 27, 2025, 09:48 AM
0 votes
0 answers
13 views
Failed to use pinctrl/pinmux in port multiplexing
I am working on an embedded Linux system (kernel-5.10.24), and now I want to setup a UART port (with RXD/TXD/RTS/CTS) from 2 GPIO groups. The DTS is setting as follows, ``` &pinctrl { uart0_pin: uart0-pin { uart0_test_porta: uart0-test-porta { rvsoc,pinmux = ; rvsoc,pinmux-funcsel = ; }; uart0_test_...
I am working on an embedded Linux system (kernel-5.10.24), and now I want to setup a UART port (with RXD/TXD/RTS/CTS) from 2 GPIO groups. The DTS is setting as follows,
&pinctrl {
    uart0_pin: uart0-pin {
        uart0_test_porta: uart0-test-porta {
            rvsoc,pinmux = ;
            rvsoc,pinmux-funcsel = ;
        };
        uart0_test_portc: uart0-test-portc {
            rvsoc,pinmux = ;
            rvsoc,pinmux-funcsel = ;
        };
    };
};

&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = ;
    pinctrl-1 = ;
};
With above settings, I found only the PORTA is configured to work in UART mode, but PORTC is NOT being configured accordingly. Is there anyting wrong in my DTS, and how can I make the combined PORTA and PORTC to work together as one 4-wire UART?
wangt13 (631 rep)
Dec 31, 2024, 07:03 AM
1 votes
0 answers
216 views
How to define GPIO pin as PPS source device
I'm using Linux in an embedded environment. I want to use a particular GPIO pin as a PPS source to be used with `gpsd`. I think the pin is configured correctly because I can see high/low transitions using ```lang-sh cat /sys/class/gpio/gpio372/value ``` I have also defined the GPIO pin as a PPS sour...
I'm using Linux in an embedded environment. I want to use a particular GPIO pin as a PPS source to be used with gpsd. I think the pin is configured correctly because I can see high/low transitions using
-sh
cat /sys/class/gpio/gpio372/value
I have also defined the GPIO pin as a PPS source in the device tree.
/ {
chosen {
    bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4";
};

pps-gpio {
    compatible = "pps-gpio";
    gpios = ;
    echo-gpios = ;
	echo-active-ms = ;
};};
Usually you would tell gpsd to use /dev/pps0, but I am not sure how to assign the GPIO to a device under /dev or how else to configure it.
R Mason (11 rep)
Dec 4, 2024, 06:05 PM • Last activity: Dec 5, 2024, 11:19 AM
0 votes
2 answers
61 views
How to read a pin state on a Raspberry Pi and select (either/or) between two entries in a cron file?
So I built an MLB scoreboard with a RPI 3. Right now the display can either show my favorite teams playing, OR all the teams playing and cycle through them as they are going on. I would like to add a toggle switch to the back of the scoreboard to select either the All-Teams or My-Teams depending on...
So I built an MLB scoreboard with a RPI 3. Right now the display can either show my favorite teams playing, OR all the teams playing and cycle through them as they are going on. I would like to add a toggle switch to the back of the scoreboard to select either the All-Teams or My-Teams depending on who's playing at that time. I have two separate nearly identical directories for the scoreboard accommodating either All-Teams or My-Teams. Right now I have a cron file called "start-scoreboard.sh" that will start the score board showing either All-Teams, or My-Teams depending on what I have entered into "cd /home/pi/mlb-led-scoreboard-(All-Teams OR My-Teams here)" within the cron file. Is there a way for the RPI to read the state of a gpio pin and run either All-Teams OR My-Teams directories? Here is an example of what I have in my "start-scoreboard.sh" cron file with the All-Teams directory listed...
!/bin/bash
u/reboot sleep 60 $$ start-scoreboard.sh
cd /home/pi/mlb-led-scoreboard-All-Teams
n=0
until [ $n -ge 10 ]
do
python main.py --led-gpio-mapping=adafruit-hat --led-brightness=50 --led-slowdown-gpio=3 --led-rows=32 --led-cols=64 && break
n=$[$n+1]
sleep 10
done
Joe Schmo (3 rep)
Apr 14, 2024, 07:45 AM • Last activity: Apr 14, 2024, 10:56 PM
0 votes
0 answers
511 views
RPi.GPIO RuntimeError
So I am getting an error: ```RuntimeError: Not running on a RPi!``` When the python script tries to setup a pin. This script worked when I ran it from an SD card, but now I have an external SSD connected via a USB port, and the script doesn't work anymore. I have been searching for an answer for qui...
So I am getting an error:
: Not running on a RPi!
When the python script tries to setup a pin. This script worked when I ran it from an SD card, but now I have an external SSD connected via a USB port, and the script doesn't work anymore. I have been searching for an answer for quite a while, but haven't found anything on how to fix it. The RaspberryPI 4 is using the SSD as a primary data storage. import RPi.GPIO as GPIO GPIO.setmode(GPIO.BOARD) GPIO.setup(2, GPIO.OUT)
Mato (1 rep)
Jan 17, 2022, 02:19 PM • Last activity: Feb 13, 2024, 10:47 PM
1 votes
0 answers
450 views
Unable to access GPIOs on Intel Alder Lake CPU
I have a motherboard with an Alder Lake CPU on it (i9-12900E). I'm trying to access the GPIO pins on the motherboard using [libgpiod][1]. After I boot linux, I have no `/dev/gpiochipX` entries. When I run `gpiodetect` I get no output. I can see the following in `dmesg`: ``` [ 1.389872] pinctrl core:...
I have a motherboard with an Alder Lake CPU on it (i9-12900E). I'm trying to access the GPIO pins on the motherboard using libgpiod . After I boot linux, I have no /dev/gpiochipX entries. When I run gpiodetect I get no output. I can see the following in dmesg:
[    1.389872] pinctrl core: initialized pinctrl subsystem
I see the following in DebugFS :
# cat /sys/kernel/debug/gpio 

# cat /sys/kernel/debug/pinctrl/pinctrl-devices 
name [pinmux] [pinconf]

# cat /sys/kernel/debug/pinctrl/pinctrl-handles 
Requested pin control handlers their pinmux maps:

# cat /sys/kernel/debug/pinctrl/pinctrl-maps 
Pinctrl maps:
I have Alder Lake pinctrl support compiled into my kernel:
# zgrep -i PINCTRL_ALDERLAKE /proc/config.gz
CONFIG_PINCTRL_ALDERLAKE=y
So pinctrl is being initialized, but somehow the pins aren't getting detected. What else can I try to dig deeper into this issue? For reference I'm running linux kernel version 6.5.2-artix1-1.
bcattle (131 rep)
Sep 19, 2023, 10:51 PM • Last activity: Sep 20, 2023, 12:37 AM
8 votes
4 answers
6666 views
Set GPIO permissions cleanly
Can the default permissions and ownership of /sys/class/gpio/ files be set, e.g. by configuring udev? The point would be to have a real gid for processes that can access GPIO pins on a board. Most "solutions" include suid wrappers, [scripts with chown][1] and trusted middleman binaries. Web searches...
Can the default permissions and ownership of /sys/class/gpio/ files be set, e.g. by configuring udev? The point would be to have a real gid for processes that can access GPIO pins on a board. Most "solutions" include suid wrappers, scripts with chown and trusted middleman binaries. Web searches turn up failed attempts to write udev rules . (related: Q1 ) (resources: avrfreaks , linux , udev )
XTL (1212 rep)
Jul 2, 2012, 11:51 AM • Last activity: Jul 19, 2023, 10:20 AM
0 votes
2 answers
912 views
Docker with device [GPIO] access while running as a specified user that is not `root`
We have an internal test system for our multi-platform software library which runs, with its zoo of third-party tools, inside an [Ubuntu] Docker container. To keep things simple, the host machine AND the Docker container both run as the same user, `test_user`, fixed as UID 1000/GID 1000; this way vo...
We have an internal test system for our multi-platform software library which runs, with its zoo of third-party tools, inside an [Ubuntu] Docker container. To keep things simple, the host machine AND the Docker container both run as the same user, test_user, fixed as UID 1000/GID 1000; this way volumes can be mapped between the two without issue. However, I now need the Docker container to be able to access *devices* on the host machine (e.g. GPIOs). This ONLY seems to work if I run the Docker container specifically as the root user, i.e. this successfully permits access to GPIOs on the host (as evidenced by gpiodetect, which uses the libgpio API that we now want to test):
docker run -t --rm -i -u root --privileged -v /sys:/sys -v /dev:/dev docker_image /bin/bash
root@f1f7ca240c1e:/workdir# gpiodetect
gpiochip0 [pinctrl-bcm2711] (58 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)
...whereas, if I add test_user to the root group, both inside the Docker container *and* on the host machine, none of these do:
docker run -t --rm -i -u test_user --privileged -v /sys:/sys -v /dev:/dev docker_image /bin/bash
test_user@57130c86c196:/workdir$ gpiodetect
gpiodetect: unable to access GPIO chips: Permission denied
docker run -t --rm -i -u 1000:1000 --privileged -v /sys:/sys -v /dev:/dev docker_image /bin/bash`
test_user@57130c86c196:/workdir$ gpiodetect
gpiodetect: unable to access GPIO chips: Permission denied
Unfortunately running the Docker container as root, as well as not being desirable, is not an easy option as it messes up the mappings with the host machine (lots of shouts of dubious users from Git and the like). Is there a way to persuade Docker that the [non-root] user it is running as has root privileges and so can have GPIO access? FYI, on the host:
host:~ $ groups test_user
test_user: test_user root
...and in the Docker container:
test_user@1716f343e8c7:/workdir$ groups test_user
test_user: test_user root
test_user@1716f343e8c7:/workdir$
Rob (103 rep)
Jul 4, 2023, 04:33 PM • Last activity: Jul 6, 2023, 10:41 AM
0 votes
2 answers
1598 views
gpiomon from libgpiod library seem not to behave correctly
I am trying to monitor GPIO events using `libgpiod` on the armbian linux. Accourding to [pinout of my device (BananaPI Zero M2 /w H3 allwinner chip)][1] I have pin `CON2-P18 ` hooked on impulse button so I can test it. First I'll find what line the pin is: ``` # gpiofind "CON2-P18" gpiochip0 68 ```...
I am trying to monitor GPIO events using libgpiod on the armbian linux. Accourding to pinout of my device (BananaPI Zero M2 /w H3 allwinner chip) I have pin CON2-P18 hooked on impulse button so I can test it. First I'll find what line the pin is:
# gpiofind "CON2-P18"
gpiochip0 68
then I'll try to monitor it with gpiomon with no luck:
# gpiomon --rising-edge 0 68
gpiomon: error waiting for events: No such device
however I can confirm gpioget utility recognizes it and behaves properly on the button event:
# gpioget --bias=pull-down 0 68
0
root@bananapim2zero:~# gpioget --bias=pull-down 0 68
1
root@bananapim2zero:~# gpioget --bias=pull-down 0 68
0
I can also set it using gpioset utility:
# gpioset -B pull-down gpiochip0 68=0
I have verified the same process for monitoring CON2-P16 and that all works fine! The same error and behavior I get when I try to monitor pin CON2-P18. To simply use another pin would be kind of troublesome for me, because I have already designed and got manufactured PCBs using forementioned pins/lines. And I would like to use gpiomon utility instead of endless looping on gpioget. Am I doing something wrong or is the lib faulty? libgpiod v. 1.6.2-1
# uname -a
Linux bananapim2zero 6.1.11-sunxi #23.02.2 SMP Sat Feb 18 05:52:53 UTC 2023 armv7l GNU/Linux
thanks! EDIT: providing strace of 'faulty' command: $ strace sudo gpiomon --bias=pull-down --rising-edge gpiochip0 68:
execve("/usr/bin/sudo", ["sudo", "gpiomon", "--bias=pull-down", "--rising-edge", "gpiochip0", "68"], 0xbed32684 /* 24 vars */) = 0
access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
brk(NULL)                               = 0x192b000
fcntl64(0, F_GETFD)                     = 0
fcntl64(1, F_GETFD)                     = 0
fcntl64(2, F_GETFD)                     = 0
access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6fd6000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/v7l/neon/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/v7l/neon/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/v7l/neon/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/v7l/neon", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/v7l/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/v7l/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/v7l/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/v7l", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/neon/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/neon/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/neon/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/neon", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/tls/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/tls", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/v7l/neon/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/v7l/neon/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/v7l/neon/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/v7l/neon", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/v7l/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/v7l/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/v7l/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/v7l", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/neon/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/neon/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/neon/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/neon", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/vfp/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo/vfp", 0xbea15a10) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/sudo/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/sudo", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=24663, ...}) = 0
mmap2(NULL, 24663, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6fcf000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libaudit.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\364\"\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=107848, ...}) = 0
mmap2(NULL, 237700, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6f74000
mprotect(0xb6f8d000, 65536, PROT_NONE)  = 0
mmap2(0xb6f9d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0xb6f9d000
mmap2(0xb6f9f000, 61572, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6f9f000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/sudo/libselinux.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libselinux.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0@R\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=108036, ...}) = 0
mmap2(NULL, 179236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6f48000
mprotect(0xb6f61000, 65536, PROT_NONE)  = 0
mmap2(0xb6f71000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0xb6f71000
mmap2(0xb6f73000, 3108, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6f73000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/sudo/libutil.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libutil.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\200\n\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=9800, ...}) = 0
mmap2(NULL, 73908, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6f35000
mprotect(0xb6f37000, 61440, PROT_NONE)  = 0
mmap2(0xb6f46000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0xb6f46000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/sudo/libsudo_util.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\320=\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=79628, ...}) = 0
mmap2(NULL, 145056, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6f11000
mprotect(0xb6f24000, 61440, PROT_NONE)  = 0
mmap2(0xb6f33000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12000) = 0xb6f33000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/sudo/libpthread.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libpthread.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\25K\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=113596, ...}) = 0
mmap2(NULL, 152152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6eeb000
mprotect(0xb6efe000, 61440, PROT_NONE)  = 0
mmap2(0xb6f0d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12000) = 0xb6f0d000
mmap2(0xb6f0f000, 4696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6f0f000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/sudo/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\331v\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=971712, ...}) = 0
mmap2(NULL, 1040988, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6dec000
mprotect(0xb6ed6000, 61440, PROT_NONE)  = 0
mmap2(0xb6ee5000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe9000) = 0xb6ee5000
mmap2(0xb6ee8000, 8796, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6ee8000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libcap-ng.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0H\16\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=13860, ...}) = 0
mmap2(NULL, 78068, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6dd8000
mprotect(0xb6ddb000, 61440, PROT_NONE)  = 0
mmap2(0xb6dea000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0xb6dea000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libpcre2-8.so.0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\250\31\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=415588, ...}) = 0
mmap2(NULL, 479836, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6d62000
mprotect(0xb6dc7000, 61440, PROT_NONE)  = 0
mmap2(0xb6dd6000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x64000) = 0xb6dd6000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libdl.so.2", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0000\n\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=9772, ...}) = 0
mmap2(NULL, 73924, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6d4f000
mprotect(0xb6d51000, 61440, PROT_NONE)  = 0
mmap2(0xb6d60000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0xb6d60000
close(3)                                = 0
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6fcd000
set_tls(0xb6fcdbd0)                     = 0
mprotect(0xb6ee5000, 8192, PROT_READ)   = 0
mprotect(0xb6d60000, 4096, PROT_READ)   = 0
mprotect(0xb6f0d000, 4096, PROT_READ)   = 0
mprotect(0xb6dd6000, 4096, PROT_READ)   = 0
mprotect(0xb6dea000, 4096, PROT_READ)   = 0
mprotect(0xb6f33000, 4096, PROT_READ)   = 0
mprotect(0xb6f46000, 4096, PROT_READ)   = 0
mprotect(0xb6f71000, 4096, PROT_READ)   = 0
mprotect(0xb6f9d000, 4096, PROT_READ)   = 0
mprotect(0x43f000, 4096, PROT_READ)     = 0
mprotect(0xb6fd8000, 4096, PROT_READ)   = 0
munmap(0xb6fcf000, 24663)               = 0
set_tid_address(0xb6fcd778)             = 10974
set_robust_list(0xb6fcd780, 12)         = 0
rt_sigaction(SIGRTMIN, {sa_handler=0xb6eef66d, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0xb6e13841}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0xb6eef6f1, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0xb6e13841}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
ugetrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM_INFINITY}) = 0
statfs("/sys/fs/selinux", 0xbea1655c)   = -1 ENOENT (No such file or directory)
statfs("/selinux", {f_type=EXT2_SUPER_MAGIC, f_bsize=4096, f_blocks=7438627, f_bfree=6853088, f_bavail=6769594, f_files=1855392, f_ffree=1745592, f_fsid={val=[2118024466, 2550696797]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_NOATIME}) = 0
brk(NULL)                               = 0x192b000
brk(0x194c000)                          = 0x194c000
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"..., 1024) = 386
read(3, "", 1024)                       = 0
close(3)                                = 0
access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
prlimit64(0, RLIMIT_AS, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_AS, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = 0
prlimit64(0, RLIMIT_CORE, NULL, {rlim_cur=0, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_CPU, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_CPU, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = 0
prlimit64(0, RLIMIT_DATA, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_DATA, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = 0
prlimit64(0, RLIMIT_FSIZE, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_FSIZE, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = 0
prlimit64(0, RLIMIT_LOCKS, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_MEMLOCK, NULL, {rlim_cur=65536*1024, rlim_max=65536*1024}) = 0
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=1024, rlim_max=1024*1024}) = 0
prlimit64(0, RLIMIT_NOFILE, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = -1 EPERM (Operation not permitted)
prlimit64(0, RLIMIT_NOFILE, {rlim_cur=256, rlim_max=RLIM64_INFINITY}, NULL) = -1 EPERM (Operation not permitted)
prlimit64(0, RLIMIT_NOFILE, {rlim_cur=1024*1024, rlim_max=1024*1024}, NULL) = 0
prlimit64(0, RLIMIT_NPROC, NULL, {rlim_cur=2962, rlim_max=2962}) = 0
prlimit64(0, RLIMIT_NPROC, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = -1 EPERM (Operation not permitted)
prlimit64(0, RLIMIT_NPROC, {rlim_cur=2962, rlim_max=2962}, NULL) = 0
prlimit64(0, RLIMIT_RSS, NULL, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_RSS, {rlim_cur=RLIM64_INFINITY, rlim_max=RLIM64_INFINITY}, NULL) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
fcntl64(0, F_GETFL)                     = 0x2 (flags O_RDWR)
fcntl64(1, F_GETFL)                     = 0x2 (flags O_RDWR)
fcntl64(2, F_GETFL)                     = 0x2 (flags O_RDWR)
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=3041456, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6b4f000
mmap2(NULL, 2596864, PROT_READ, MAP_PRIVATE, 3, 0x6d000) = 0xb68d5000
close(3)                                = 0
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2329, ...}) = 0
fstat64(3, {st_mode=S_IFREG|0644, st_size=2329, ...}) = 0
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\0\0\0\10\0\0\0\0"..., 4096) = 2329
_llseek(3, -1479, , SEEK_CUR)      = 0
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\t\0\0\0\t\0\0\0\0"..., 4096) = 1479
close(3)                                = 0
stat64("/etc/sudo.conf", {st_mode=S_IFREG|0644, st_size=3975, ...}) = 0
openat(AT_FDCWD, "/etc/sudo.conf", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=3975, ...}) = 0
read(3, "#\n# Default /etc/sudo.conf file\n"..., 4096) = 3975
read(3, "", 4096)                       = 0
close(3)                                = 0
geteuid32()                             = 1000
access("/usr/local/bin/sudo", X_OK)     = -1 ENOENT (No such file or directory)
access("/usr/bin/sudo", X_OK)           = 0
stat64("/usr/bin/sudo", {st_mode=S_IFREG|S_ISUID|0755, st_size=135928, ...}) = 0
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2996, ...}) = 0
read(3, "# Locale name alias data base.\n#"..., 4096) = 2996
read(3, "", 4096)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/usr/share/locale/en_US.UTF-8/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en_US.utf8/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en_US/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en.UTF-8/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en.utf8/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/en/LC_MESSAGES/sudo.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
write(2, "sudo", 4sudo)                     = 4
write(2, ": ", 2: )                       = 2
write(2, "effective uid is not 0, is /usr/"..., 133effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?) = 133
ioctl(2, TCGETS, {B9600 opost isig icanon echo ...}) = 0
)                       = 1
write(2, "\n", 1
)                       = 1
exit_group(1)                           = ?
+++ exited with 1 +++
'working' $ sudo strace gpiomon --bias=pull-down --rising-edge gpiochip0 15 command:
execve("/usr/bin/gpiomon", ["gpiomon", "--bias=pull-down", "--rising-edge", "gpiochip0", "15"], 0xbed83760 /* 16 vars */) = 0
brk(NULL)                               = 0x17ce000
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f11000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=24663, ...}) = 0
mmap2(NULL, 24663, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6f0a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libgpiod.so.2", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\260!\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=21952, ...}) = 0
mmap2(NULL, 86024, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6ed4000
mprotect(0xb6ed9000, 61440, PROT_NONE)  = 0
mmap2(0xb6ee8000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0xb6ee8000
close(3)                                = 0
openat(AT_FDCWD, "/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\331v\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=971712, ...}) = 0
mmap2(NULL, 1040988, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6dd5000
mprotect(0xb6ebf000, 61440, PROT_NONE)  = 0
mmap2(0xb6ece000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe9000) = 0xb6ece000
mmap2(0xb6ed1000, 8796, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6ed1000
close(3)                                = 0
set_tls(0xb6f12180)                     = 0
mprotect(0xb6ece000, 8192, PROT_READ)   = 0
mprotect(0xb6ee8000, 4096, PROT_READ)   = 0
mprotect(0x4a1000, 4096, PROT_READ)     = 0
mprotect(0xb6f13000, 4096, PROT_READ)   = 0
munmap(0xb6f0a000, 24663)               = 0
rt_sigprocmask(SIG_BLOCK, [INT TERM], NULL, 8) = 0
signalfd4(-1, [INT TERM], 8, 0)         = 3
openat(AT_FDCWD, "/dev", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 4
fstat64(4, {st_mode=S_IFDIR|0755, st_size=3300, ...}) = 0
brk(NULL)                               = 0x17ce000
brk(0x17ef000)                          = 0x17ef000
getdents64(4, 0x17ce1b0 /* 165 entries */, 32768) = 4896
getdents64(4, 0x17ce1b0 /* 0 entries */, 32768) = 0
close(4)                                = 0
openat(AT_FDCWD, "/dev/gpiochip0", O_RDWR|O_CLOEXEC) = 4
lstat64("/dev/gpiochip0", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0), ...}) = 0
stat64("/dev/gpiochip0", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0), ...}) = 0
access("/sys/bus/gpio/devices/gpiochip0/dev", R_OK) = 0
openat(AT_FDCWD, "/sys/bus/gpio/devices/gpiochip0/dev", O_RDONLY) = 5
read(5, "254:0\n", 15)                  = 6
close(5)                                = 0
ioctl(4, GPIO_GET_CHIPINFO_IOCTL, 0xbee98018) = 0
openat(AT_FDCWD, "/dev/gpiochip1", O_RDWR|O_CLOEXEC) = 5
lstat64("/dev/gpiochip1", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0x1), ...}) = 0
stat64("/dev/gpiochip1", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0x1), ...}) = 0
access("/sys/bus/gpio/devices/gpiochip1/dev", R_OK) = 0
openat(AT_FDCWD, "/sys/bus/gpio/devices/gpiochip1/dev", O_RDONLY) = 6
read(6, "254:1\n", 15)                  = 6
close(6)                                = 0
ioctl(5, GPIO_GET_CHIPINFO_IOCTL, 0xbee98018) = 0
close(4)                                = 0
close(5)                                = 0
openat(AT_FDCWD, "/dev/gpiochip0", O_RDWR|O_CLOEXEC) = 4
lstat64("/dev/gpiochip0", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0), ...}) = 0
stat64("/dev/gpiochip0", {st_mode=S_IFCHR|0600, st_rdev=makedev(0xfe, 0), ...}) = 0
access("/sys/bus/gpio/devices/gpiochip0/dev", R_OK) = 0
openat(AT_FDCWD, "/sys/bus/gpio/devices/gpiochip0/dev", O_RDONLY) = 5
read(5, "254:0\n", 15)                  = 6
close(5)                                = 0
ioctl(4, GPIO_GET_CHIPINFO_IOCTL, 0xbee98058) = 0
ioctl(4, GPIO_GET_LINEINFO_IOCTL, 0xbee9807c) = 0
ioctl(4, GPIO_GET_LINEEVENT_IOCTL, 0xbee9809c) = 0
ioctl(4, GPIO_GET_LINEINFO_IOCTL, 0xbee9803c) = 0
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 1 ([{fd=5, revents=POLLIN}])
read(5, "\233\\D\336\201\10\0\0\1\0\0\0\0\0\0\0", 16) = 16
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "event:  RISING EDGE offset: 15 t"..., 63event:  RISING EDGE offset: 15 timestamp: [    9353.872825499]
) = 63
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 1 ([{fd=5, revents=POLLIN}])
read(5, "\213\273\312\6\203\10\0\0\1\0\0\0\0\0\0\0", 16) = 16
write(1, "event:  RISING EDGE offset: 15 t"..., 63event:  RISING EDGE offset: 15 timestamp: [    9358.847687563]
) = 63
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000) = 0 (Timeout)
poll([{fd=5, events=POLLIN|POLLPRI}, {fd=3, events=POLLIN|POLLPRI}], 2, 10000^C) = 1 ([{fd=3, revents=POLLIN}])
strace: Process 10962 detached
greengold (113 rep)
May 18, 2023, 09:41 AM • Last activity: Jun 7, 2023, 05:12 AM
1 votes
0 answers
232 views
Idiomatic GPIO control in Linux 4.8+
The userspace interface to GPIOs was [changed][1] in Linux 4.8, deprecating the old sysfs interface in favor of a character device. I am struggling to see how I can wrangle this new model into something that makes sense for my use case. I have a peripheral device that I control from my ARM CPU using...
The userspace interface to GPIOs was changed in Linux 4.8, deprecating the old sysfs interface in favor of a character device. I am struggling to see how I can wrangle this new model into something that makes sense for my use case. I have a peripheral device that I control from my ARM CPU using GPIOs. It has a bootup sequence that I must follow - first turn on the 5v regulator (a separate device), then power on the peripheral, finally release reset after at least 5 ms have passed. So I have 3 GPIOs, 5v_en, power_on and reset_n. The peripheral takes about a minute to get from power-on to operational, so there cannot be unnecessary reboots. Other requirements are that I toggle reset to complete firmware/config updates and I have a low-power mode where the peripheral and regulator are off. There are three options I see, and none of them are ideal. For these examples, assume gpio0=5v_en, gpio1=power_on and gpio2=reset_n ---------- Option #1: Sysfs (deprecated)
cd /sys/class/gpio
echo 0 > export
echo 1 > export
echo 2 > export
echo out > gpio0/direction
echo out > gpio1/direction
echo out > gpio2/direction
echo 1 > gpio0/value
echo 1 > gpio1/value
sleep 1
echo 1 > gpio2/value
Have the above in an init script. When I need to change the value of a pin, simply
the value file and write in the new value. This makes the most sense to me. Unfortunately, it is now deprecated. ---------- Option #2:
gpioset -m signal -b 1 0=1
gpioset -m signal -b 1 1=1
sleep 1
gpioset -m signal -b 1 2=1
Have the above in an init script. When I need to change the value of a pin, determine the PID of the
process,
the process and relaunch with the new value. While gpioset is not running, the state of the pin is technically "undefined." Doesn't seem like the way to go. ---------- Option #3: Daemon Instead of running gpioset, write my own custom daemon with the sole purpose of keeping an open file descriptor to the gpio character devices in /dev. The program must be simple and minimal because if it crashes, the state of all GPIOs it is managing becomes undefined. However it must also maintain some kind of IPC to talk to other programs that want to control the GPIOs. Essentially, we've slapped a layer of indirection in to keep things that want to use the GPIOs from using them directly. This feels drastically over-engineered, and drafting, implementing, and documenting the IPC protocol is really annoying compared to letting everybody use /sys/gpio as they like. ---------- I have a feeling that Option #3 is how Linux intends us to manage GPIOs going forward, but it slaps on a lot of extra work for no benefit in this particular use case. With this question, I hope to understand 1. Are there any other strategies I haven't considered that would make more sense? 2. How can I mitigate the risk of a crashed program leading to undefined GPIO states?
user488486 (11 rep)
Aug 25, 2021, 09:33 PM • Last activity: May 4, 2023, 03:31 PM
1 votes
0 answers
737 views
Converting to an immutable gpio chip
I have spent the last 10 hours trying to boot asahi linux on my M1 Mac, and now I can finally run the boot command. However, when I run it, there is an error message that says `[0.202844] gpio gpiochip0: (macsmc-pmu-gpio): not an immutable chip, please consider fixing it!` I haven't found any resour...
I have spent the last 10 hours trying to boot asahi linux on my M1 Mac, and now I can finally run the boot command. However, when I run it, there is an error message that says [0.202844] gpio gpiochip0: (macsmc-pmu-gpio): not an immutable chip, please consider fixing it! I haven't found any resources on how a user can fix this online. Please help.
TedFaro (11 rep)
Jan 19, 2023, 03:55 PM • Last activity: May 2, 2023, 09:23 AM
5 votes
2 answers
5024 views
Are ioctl calls blocking?
I am writing some code around libgpiod's interface. For example, I want to set a line to output high. [Under the hood](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/lib/core.c#n961), libgpiod opens an fd provided by the kernel for the line, and then calls `ioctl(fd, GPIO_V2_LINE_SET...
I am writing some code around libgpiod's interface. For example, I want to set a line to output high. [Under the hood](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/lib/core.c#n961) , libgpiod opens an fd provided by the kernel for the line, and then calls ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, ...). My questions are: - Is this particular ioctl() call (with the GPIO_V2... argument) theoretically (potentially) blocking in the same way that *writing* to an arbitrary file descriptor can be? - Are ioctl() calls in general theoretically blocking? For example, requesting the line in the first place also involves an ioctl() on a fd for the chip. What about I2C ioctl()s? - If it is blocking, is the underlying fd in the line struct (line->fd_handle->fd) the one I need to wait on in an event loop (eg. epoll() or an abstracted event library like libuv)? I have tried to answer this question through research, but (a) searching for any combination of "ioctl" and "blocking" just gives results for *setting* a fd to be blocking or not and (b) it's not in the man pages or kernel docs that I can find.
detly (5390 rep)
Jul 1, 2021, 04:39 AM • Last activity: May 2, 2023, 08:47 AM
2 votes
1 answers
3451 views
Unable to export specific gpio pins. How to check what uses GPIO pins and how to access register?
I use an i.mx6 board (yocto(jethro)) and am configuring a device tree. I changed a dts file and put the dtb file in a boot partition. I set GPIO4_IO19 in dts file as follows. &iomuxc { pinctrl-names = "default"; pinctrl-0 = ; imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins = ; }; ... At first MX6UL_...
I use an i.mx6 board (yocto(jethro)) and am configuring a device tree. I changed a dts file and put the dtb file in a boot partition. I set GPIO4_IO19 in dts file as follows. &iomuxc { pinctrl-names = "default"; pinctrl-0 = ; imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins = ; }; ... At first MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 was defined in other group (usdhcgrp) but I commented out them. After booting, I checked if gpio is successfully determined as I set. The result is echo 115 > /sys/class/gpio/export -sh: echo: write error: Device or resource busy So I checked this. cat /sys/kernel/debug/gpio GPIOs 0-31, platform/209c000.gpio, 209c000.gpio: gpio-10 (phy-reset ) out lo GPIOs 32-63, platform/20a0000.gpio, 20a0000.gpio: GPIOs 64-95, platform/20a4000.gpio, 20a4000.gpio: gpio-68 (ft5x06_irq_gpio ) in hi GPIOs 96-127, platform/20a8000.gpio, 20a8000.gpio: gpio-109 (? ) out lo gpio-115 (cd ) in lo gpio-116 (? ) out lo gpio-117 (? ) out lo gpio-118 (sysfs ) in hi GPIOs 128-159, platform/20ac000.gpio, 20ac000.gpio: gpio-128 (phy-reset ) out lo gpio-115 is used by cd. Maybe it means card detection. I want to use it as sysfs to read the state. Any other way to read it ? Furthermore, gpio-10, 68, 109, 116, 117 is used by other device. How can I use them by sysfs? I think I need to do is checking whether register is correctly set value or not. If the register value is not the same as I set, I guess the other process set the pin control.However I do not know the way of accessing a register. What I know about gpio115 is as follows mux_reg address: 0x01DC and the value. conf_reg address: 0x0468 and the value. input_reg address: 0x0000 and the value. The same as the other gpios. How can I access 0x01DC and then get the value in linux(yocto)? Thank you for your cooperation.
user8257918 (79 rep)
Sep 8, 2017, 02:23 AM • Last activity: Feb 3, 2023, 01:00 PM
0 votes
1 answers
294 views
x86: Drive a SSD1351 OLED Display
I've a bunch of SSD1351 OLED displays like this one: [![enter image description here][1]][1] [1]: https://i.sstatic.net/Tex3q.png Those are driven over a SPI Interface and I use them in the Raspberry Pi and other ARM SBCs like the NanoPi with the following libraries: - https://github.com/rm-hull/OPi...
I've a bunch of SSD1351 OLED displays like this one: enter image description here Those are driven over a SPI Interface and I use them in the Raspberry Pi and other ARM SBCs like the NanoPi with the following libraries: - https://github.com/rm-hull/OPi.GPIO - https://luma-oled.readthedocs.io/en/latest/ - https://pillow.readthedocs.io/en/stable/ Those ARM SBCs have integrated SPI that is exposed in Linux at /dev/spidevX.Y. **Is it possible to drive this screen from a standard x86 computer?** I own a FT4232H Mini Module (GPIO/UART/SPI to USB) and also found out the AK-MCP2210 (USB to SPI Bridge) however I'm not sure they will expose the screen in /dev/ nor if luma-oled can be used. Thank you.
TCB13 (771 rep)
Apr 2, 2022, 10:03 PM • Last activity: Aug 7, 2022, 08:15 PM
1 votes
0 answers
1561 views
How to access gpio-keys from a shell script
In my embedded linux system, I defined some GPIOs as keys in the device tree so I can use them as keys in my application. This works fine so far. Now I want to add an early startup script to respond to one of those keys before my application starts, but I can't access the gpios via `/sys/class/gpio`...
In my embedded linux system, I defined some GPIOs as keys in the device tree so I can use them as keys in my application. This works fine so far. Now I want to add an early startup script to respond to one of those keys before my application starts, but I can't access the gpios via /sys/class/gpio anymore, because they are already keys (trying to export them gives write error: Device or resource busy). I could install evtest or simply read /dev/input/event0 where I can see key presses and releases, but I can't see a straight forward way to detect a long key press that way in a shell script. Any suggestion how this should be done?
Philippos (13680 rep)
May 13, 2022, 08:13 AM
0 votes
2 answers
4835 views
What gpio gpiochip0 kernel error means and how to solve?
I found this in my logs: kernel: gpio gpiochip0: (gpio_aaeon): tried to insert a GPIO chip with zero lines kernel: gpiochip_add_data_with_key: GPIOs 0..-1 (gpio_aaeon) failed to register, -22 kernel: gpio-aaeon: probe of gpio-aaeon.0 failed with error -22 What does it mean and how should I solve it?...
I found this in my logs: kernel: gpio gpiochip0: (gpio_aaeon): tried to insert a GPIO chip with zero lines kernel: gpiochip_add_data_with_key: GPIOs 0..-1 (gpio_aaeon) failed to register, -22 kernel: gpio-aaeon: probe of gpio-aaeon.0 failed with error -22 What does it mean and how should I solve it? lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 21.04 Release: 21.04 Codename: hirsute
Adrian (773 rep)
Aug 20, 2021, 06:09 PM • Last activity: Mar 27, 2022, 12:31 PM
Showing page 1 of 20 total questions