Coverage for linuxpy/gpio/sim.py: 40%
54 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-19 07:05 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-19 07:05 +0200
1#
2# This file is part of the linuxpy project
3#
4# Copyright (c) 2024 Tiago Coutinho
5# Distributed under the GPLv3 license. See LICENSE for more info.
7"""
8## Configfs GPIO Simulator
10The configfs GPIO Simulator (gpio-sim) provides a way to create simulated GPIO chips
11for testing purposes. The lines exposed by these chips can be accessed using the
12standard GPIO character device interface as well as manipulated using sysfs attributes.
14To be able to create GPIOSim chips, the user will need permissions.
16Here is an example of what to put in /etc/udev/rules.d/80-gpio-sim.rules that gives
17access to users belonging to the `input` group:
19```
20KERNEL=="gpio-sim", SUBSYSTEM=="config", RUN+="/bin/chown -R root:input /sys/kernel/config/gpio-sim"
21KERNEL=="gpio-sim", SUBSYSTEM=="config", RUN+="/bin/chmod -R 775 /sys/kernel/config/gpio-sim"
22```
24See https://www.kernel.org/doc/html/latest/admin-guide/gpio/gpio-sim.html for details
25"""
27import logging
28from pathlib import Path
30from linuxpy.configfs import CONFIGFS_PATH
31from linuxpy.gpio.device import get_chip_info
32from linuxpy.types import Optional
34GPIOSIM_PATH: Optional[Path] = None if CONFIGFS_PATH is None else CONFIGFS_PATH / "gpio-sim"
36log = logging.getLogger("gpio-sim")
39def find_gpio_sim_file(num_lines=None) -> Optional[Path]:
40 """Best effort to find
42 Returns:
43 _type_: _description_
44 """
45 for path in sorted(Path("/dev").glob("gpiochip*")): 45 ↛ exitline 45 didn't return from function 'find_gpio_sim_file' because the loop on line 45 didn't complete
46 with path.open("rb") as fobj:
47 info = get_chip_info(fobj)
48 if "gpio-sim" in info.label:
49 if num_lines is None or info.lines == num_lines: 49 ↛ 45line 49 didn't jump to line 45 because the condition on line 49 was always true
50 return path
53def mkdir(path):
54 log.info("Creating %s", path)
55 path.mkdir()
58def rmdir(path):
59 log.info("Removing %s", path)
60 path.rmdir()
63class Device:
64 def __init__(self, config):
65 self.config = config
66 self.path: Path = GPIOSIM_PATH / config["name"]
68 @property
69 def live_path(self) -> Path:
70 return self.path / "live"
72 def cleanup(self):
73 if self.path.exists():
74 self.live_path.write_text("0")
75 for directory, _, _ in self.path.walk(top_down=False):
76 directory.rmdir()
78 def load_config(self):
79 mkdir(self.path)
81 for bank_id, bank in enumerate(self.config["banks"]):
82 lines = bank["lines"]
84 bpath = self.path / f"gpio-bank{bank_id}"
85 mkdir(bpath)
86 blabel = bank.get("name", f"gpio-sim-bank{bank_id}")
88 (bpath / "num_lines").write_text("16")
89 (bpath / "label").write_text(blabel)
90 for line_id, line in enumerate(lines):
91 lpath = bpath / f"line{line_id}"
92 mkdir(lpath)
93 (lpath / "name").write_text(line.get("name", f"L-{line_id}"))
94 if hog := line.get("hog"):
95 hpath = lpath / "hog"
96 mkdir(hpath)
97 (hpath / "name").write_text(hog["name"])
98 (hpath / "direction").write_text(hog["direction"])
100 @property
101 def live(self):
102 path = self.live_path
103 return path.exists() and int(self.live_path.read_bytes()) != 0