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

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. 

6 

7""" 

8## Configfs GPIO Simulator 

9 

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. 

13 

14To be able to create GPIOSim chips, the user will need permissions. 

15 

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: 

18 

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``` 

23 

24See https://www.kernel.org/doc/html/latest/admin-guide/gpio/gpio-sim.html for details 

25""" 

26 

27import logging 

28from pathlib import Path 

29 

30from linuxpy.configfs import CONFIGFS_PATH 

31from linuxpy.gpio.device import get_chip_info 

32from linuxpy.types import Optional 

33 

34GPIOSIM_PATH: Optional[Path] = None if CONFIGFS_PATH is None else CONFIGFS_PATH / "gpio-sim" 

35 

36log = logging.getLogger("gpio-sim") 

37 

38 

39def find_gpio_sim_file(num_lines=None) -> Optional[Path]: 

40 """Best effort to find 

41 

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 

51 

52 

53def mkdir(path): 

54 log.info("Creating %s", path) 

55 path.mkdir() 

56 

57 

58def rmdir(path): 

59 log.info("Removing %s", path) 

60 path.rmdir() 

61 

62 

63class Device: 

64 def __init__(self, config): 

65 self.config = config 

66 self.path: Path = GPIOSIM_PATH / config["name"] 

67 

68 @property 

69 def live_path(self) -> Path: 

70 return self.path / "live" 

71 

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() 

77 

78 def load_config(self): 

79 mkdir(self.path) 

80 

81 for bank_id, bank in enumerate(self.config["banks"]): 

82 lines = bank["lines"] 

83 

84 bpath = self.path / f"gpio-bank{bank_id}" 

85 mkdir(bpath) 

86 blabel = bank.get("name", f"gpio-sim-bank{bank_id}") 

87 

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"]) 

99 

100 @property 

101 def live(self): 

102 path = self.live_path 

103 return path.exists() and int(self.live_path.read_bytes()) != 0