ekfsm 0.11.0b1.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ekfsm might be problematic. Click here for more details.
- ekfsm/__init__.py +13 -0
- ekfsm/boards/oem/ekf/ccu.yaml +68 -0
- ekfsm/boards/oem/ekf/sc5-festival.yaml +30 -0
- ekfsm/boards/oem/ekf/sc9-toccata.yaml +31 -0
- ekfsm/boards/oem/ekf/spv-mystic.yaml +68 -0
- ekfsm/boards/oem/ekf/sq1-track.yaml +41 -0
- ekfsm/boards/oem/ekf/srf-fan.yaml +48 -0
- ekfsm/boards/oem/ekf/sur-uart.yaml +72 -0
- ekfsm/boards/oem/hitron/hdrc-300.yaml +20 -0
- ekfsm/cli.py +111 -0
- ekfsm/config.py +37 -0
- ekfsm/core/__init__.py +4 -0
- ekfsm/core/components.py +120 -0
- ekfsm/core/probe.py +10 -0
- ekfsm/core/slots.py +201 -0
- ekfsm/core/sysfs.py +91 -0
- ekfsm/core/utils.py +77 -0
- ekfsm/devices/__init__.py +28 -0
- ekfsm/devices/eeprom.py +1054 -0
- ekfsm/devices/ekf_ccu_uc.py +390 -0
- ekfsm/devices/ekf_sur_led.py +67 -0
- ekfsm/devices/generic.py +245 -0
- ekfsm/devices/gpio.py +340 -0
- ekfsm/devices/hwmon.py +71 -0
- ekfsm/devices/iio.py +58 -0
- ekfsm/devices/iio_thermal_humidity.py +41 -0
- ekfsm/devices/mux.py +39 -0
- ekfsm/devices/pmbus.py +65 -0
- ekfsm/devices/smbios.py +38 -0
- ekfsm/devices/utils.py +16 -0
- ekfsm/exceptions.py +58 -0
- ekfsm/log.py +28 -0
- ekfsm/py.typed +2 -0
- ekfsm/simctrl.py +241 -0
- ekfsm/system.py +326 -0
- ekfsm-0.11.0b1.post3.dist-info/METADATA +86 -0
- ekfsm-0.11.0b1.post3.dist-info/RECORD +39 -0
- ekfsm-0.11.0b1.post3.dist-info/WHEEL +4 -0
- ekfsm-0.11.0b1.post3.dist-info/entry_points.txt +2 -0
ekfsm/core/slots.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from ekfsm.core.components import HwModule
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from munch import Munch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SlotType(Enum):
|
|
12
|
+
"""
|
|
13
|
+
Define the types of slots that can be found in a chassis.
|
|
14
|
+
|
|
15
|
+
The following slot types are defined:
|
|
16
|
+
- CPCI_S0_UTILITY: CompactPCI Serial Utility Connector
|
|
17
|
+
- CPCI_S0_SYS: CompactPCI Serial System Slot
|
|
18
|
+
- CPCI_S0_PER: CompactPCI Serial Peripheral Slot
|
|
19
|
+
- CPCI_S0_PSU: CompactPCI Serial Power Supply Slot
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
CPCI_S0_UTILITY = 1 # CompactPCI Serial Utility Connector
|
|
23
|
+
CPCI_S0_SYS = 2 # CompactPCI Serial System Slot
|
|
24
|
+
CPCI_S0_PER = 3 # CompactPCI Serial Peripheral Slot
|
|
25
|
+
CPCI_S0_PSU = 4 # CompactPCI Serial Power Supply Slot
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_string(cls, name: str) -> "SlotType":
|
|
29
|
+
try:
|
|
30
|
+
return cls[name]
|
|
31
|
+
except KeyError:
|
|
32
|
+
raise ValueError(f"Invalid {cls.__name__}: {name}")
|
|
33
|
+
|
|
34
|
+
def to_string(self) -> str:
|
|
35
|
+
return self.name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Slot:
|
|
39
|
+
"""
|
|
40
|
+
A slot represents a physical slot in a chassis.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
name
|
|
45
|
+
The name of the slot, e.g. "SlotA" or "CPCI-SYSTEMSLOT"
|
|
46
|
+
slot_type
|
|
47
|
+
The type of the slot, e.g. SlotType.CPCI_S0_SYS
|
|
48
|
+
desired_hwmodule_type
|
|
49
|
+
The desired type of the hardware module that should be in the slot (currently unused)
|
|
50
|
+
desired_hwmodule_name
|
|
51
|
+
The name to be used for the hardware module instance in the slot. (currently unused)
|
|
52
|
+
master
|
|
53
|
+
The master board of the system
|
|
54
|
+
hwmodule
|
|
55
|
+
The hardware module that is in the slot
|
|
56
|
+
number
|
|
57
|
+
The number of the slot
|
|
58
|
+
attributes
|
|
59
|
+
Additional attributes
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
name: str,
|
|
65
|
+
slot_type: SlotType,
|
|
66
|
+
desired_hwmodule_type: str,
|
|
67
|
+
desired_hwmodule_name: str,
|
|
68
|
+
number: int,
|
|
69
|
+
hwmodule: HwModule | None = None,
|
|
70
|
+
master: HwModule | None = None,
|
|
71
|
+
attributes: Munch | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
self._name = name
|
|
74
|
+
self.slot_type = slot_type
|
|
75
|
+
self._desired_hwmodule_type = desired_hwmodule_type
|
|
76
|
+
self._desired_hwmodule_name = desired_hwmodule_name
|
|
77
|
+
self.number = number
|
|
78
|
+
self.hwmodule = hwmodule
|
|
79
|
+
self.master = master
|
|
80
|
+
self.attributes = attributes
|
|
81
|
+
|
|
82
|
+
def info(self) -> dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Returns a dictionary with information about the slot.
|
|
85
|
+
|
|
86
|
+
- name (str): The name of the slot
|
|
87
|
+
- slot_type (str): The type of the slot
|
|
88
|
+
- number (int): The number of the slot
|
|
89
|
+
- desired_hwmodule_type (str): The desired type of the hardware module
|
|
90
|
+
- actual_hwmodule_type (str): The actual type of the hardware module
|
|
91
|
+
- desired_hwmodule_name (str): The desired name of the hardware module
|
|
92
|
+
- is_populated (bool): Is the slot populated?
|
|
93
|
+
- is_correctly_populated (bool): Is the slot correctly populated?
|
|
94
|
+
"""
|
|
95
|
+
return {
|
|
96
|
+
"name": self._name,
|
|
97
|
+
"slot_type": self.slot_type.to_string(),
|
|
98
|
+
"number": self.number,
|
|
99
|
+
"desired_hwmodule_type": self._desired_hwmodule_type,
|
|
100
|
+
"actual_hwmodule_type": self.hwmodule.board_type if self.hwmodule else None,
|
|
101
|
+
"desired_hwmodule_name": self._desired_hwmodule_name,
|
|
102
|
+
"is_populated": self.is_populated,
|
|
103
|
+
"is_correctly_populated": self.is_correctly_populated,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
return (
|
|
108
|
+
f"{self.__class__.__name__}(name={self._name}, slot_type={self.slot_type})"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def name(self) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Return the name of the slot.
|
|
115
|
+
"""
|
|
116
|
+
return self._name
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def is_populated(self) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Return True if the slot is populated, False otherwise.
|
|
122
|
+
"""
|
|
123
|
+
return self.hwmodule is not None
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def is_correctly_populated(self) -> bool:
|
|
127
|
+
"""
|
|
128
|
+
Return True if the slot is populated with the desired hardware module type, False otherwise.
|
|
129
|
+
"""
|
|
130
|
+
return (
|
|
131
|
+
self.hwmodule is not None
|
|
132
|
+
and self.hwmodule.board_type.lower() == self._desired_hwmodule_type.lower()
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Slots(Munch):
|
|
137
|
+
"""
|
|
138
|
+
A collection of slots.
|
|
139
|
+
|
|
140
|
+
Slots are stored in a dictionary-like object, where the key is the slot name and the value is the Slot object.
|
|
141
|
+
Slots can be accessed by name, by number or via an attribute access matching the key.
|
|
142
|
+
|
|
143
|
+
Example
|
|
144
|
+
-------
|
|
145
|
+
>>> from ekfsm.slot import Slot, Slots, SlotType
|
|
146
|
+
>>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
|
|
147
|
+
>>> slots = Slots((slotA.name, slotA))
|
|
148
|
+
>>> print(slots[name])
|
|
149
|
+
>>> print(slots.slotA) # attribute access, same as slots[name]
|
|
150
|
+
>>> print(slots[3]) # number access, same as slots.slotA
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
154
|
+
super().__init__(*args, **kwargs)
|
|
155
|
+
|
|
156
|
+
def __getitem__(self, key: int | str) -> Slot:
|
|
157
|
+
"""
|
|
158
|
+
Get a slot by name, number or attribute access.
|
|
159
|
+
"""
|
|
160
|
+
if isinstance(key, int):
|
|
161
|
+
return next(slot for slot in self.values() if slot.number == key)
|
|
162
|
+
|
|
163
|
+
return super().__getitem__(key)
|
|
164
|
+
|
|
165
|
+
def __setitem__(self, key, value):
|
|
166
|
+
"""
|
|
167
|
+
Add a Slot object to the collection.
|
|
168
|
+
|
|
169
|
+
Raises
|
|
170
|
+
------
|
|
171
|
+
ValueError
|
|
172
|
+
if:
|
|
173
|
+
- the value is not a Slot object
|
|
174
|
+
- the key does not match the slot name
|
|
175
|
+
- the slot already exists in collection
|
|
176
|
+
- or the slot number is not unique
|
|
177
|
+
"""
|
|
178
|
+
if not isinstance(value, Slot):
|
|
179
|
+
raise ValueError("Only Slot instances can be added to a Slots collection.")
|
|
180
|
+
elif key != value.name:
|
|
181
|
+
raise ValueError("Slot name must match key.")
|
|
182
|
+
elif value in self.values():
|
|
183
|
+
raise ValueError("Slot already exists in collection.")
|
|
184
|
+
elif value.number in [slot.number for slot in self.values()]:
|
|
185
|
+
raise ValueError("Slot number must be unique.")
|
|
186
|
+
|
|
187
|
+
return super().__setitem__(key, value)
|
|
188
|
+
|
|
189
|
+
def add(self, slot: Slot) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Add a Slot object to the collection, where the name of the slot object is used as the key.
|
|
192
|
+
|
|
193
|
+
Example
|
|
194
|
+
-------
|
|
195
|
+
>>> from ekfsm.slot import Slot, Slots, SlotType
|
|
196
|
+
>>> slotA = Slot("SlotA", SlotType.CPCI_S0_PER, "Bla", "Blubb", 3)
|
|
197
|
+
>>> slots = Slots()
|
|
198
|
+
>>> slots.add(slotA) # add slotA to the collection
|
|
199
|
+
>>> print(slots.SlotA) # attribute access, same as slots["SlotA"]
|
|
200
|
+
"""
|
|
201
|
+
self[slot.name] = slot
|
ekfsm/core/sysfs.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
SYSFS_ROOT = Path("/sys")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sysfs_root() -> Path:
|
|
7
|
+
return SYSFS_ROOT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def set_sysfs_root(path: Path) -> None:
|
|
11
|
+
global SYSFS_ROOT
|
|
12
|
+
SYSFS_ROOT = path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def file_is_sysfs_attr(path: Path) -> bool:
|
|
16
|
+
return path.is_file() and not path.stat().st_mode & 0o111
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SysFSAttribute:
|
|
20
|
+
"""
|
|
21
|
+
A SysFSAttribute is a singular sysfs attribute located somewhere in */sys*.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
path : Path
|
|
26
|
+
path to the underlying file for the SysFSAttribute instance
|
|
27
|
+
name : str
|
|
28
|
+
attribute name, or the last part of self.path
|
|
29
|
+
|
|
30
|
+
Methods
|
|
31
|
+
-------
|
|
32
|
+
read() -> Optional[str]
|
|
33
|
+
Reads the file attribute if it exists
|
|
34
|
+
write(data: Union[str, int]) -> None
|
|
35
|
+
Writes data to the file attribute
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, path: Path):
|
|
40
|
+
self.path = path
|
|
41
|
+
if not path.exists() or not path.is_file():
|
|
42
|
+
raise FileNotFoundError("Invalid sysfs attribute path")
|
|
43
|
+
|
|
44
|
+
self.name: str = path.name
|
|
45
|
+
|
|
46
|
+
def read_utf8(self) -> str:
|
|
47
|
+
return self.path.read_text()
|
|
48
|
+
|
|
49
|
+
def read_bytes(self) -> bytes:
|
|
50
|
+
return self.path.read_bytes()
|
|
51
|
+
|
|
52
|
+
def write(self, data: str | bytes | None, offset: int = 0) -> None:
|
|
53
|
+
if self.is_sysfs_attr() and data is not None:
|
|
54
|
+
mode = 'r+' if isinstance(data, str) else 'rb+'
|
|
55
|
+
with open(self.path, mode) as f:
|
|
56
|
+
f.seek(offset)
|
|
57
|
+
f.write(data)
|
|
58
|
+
|
|
59
|
+
def is_sysfs_attr(self) -> bool:
|
|
60
|
+
return file_is_sysfs_attr(self.path)
|
|
61
|
+
|
|
62
|
+
def __repr__(self):
|
|
63
|
+
return f"SysFSAttribute: {self.name}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def list_sysfs_attributes(path: Path) -> list[SysFSAttribute]:
|
|
67
|
+
if not path.exists() or not path.is_dir():
|
|
68
|
+
raise FileNotFoundError(f"Invalid sysfs directory: {path}")
|
|
69
|
+
|
|
70
|
+
return [SysFSAttribute(item) for item in path.iterdir() if file_is_sysfs_attr(item)]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SysFSDevice:
|
|
74
|
+
def __init__(self, base_dir: Path):
|
|
75
|
+
self.path: Path = base_dir
|
|
76
|
+
self.attributes: list[SysFSAttribute] = list_sysfs_attributes(self.path)
|
|
77
|
+
|
|
78
|
+
def pre(self) -> None:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def post(self) -> None:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def write_attr(self, attr: str, data: str | bytes, offset: int = 0) -> None:
|
|
85
|
+
next(x for x in self.attributes if x.name == attr).write(data, offset)
|
|
86
|
+
|
|
87
|
+
def read_attr_utf8(self, attr: str) -> str:
|
|
88
|
+
return next(x for x in self.attributes if x.name == attr).read_utf8()
|
|
89
|
+
|
|
90
|
+
def read_attr_bytes(self, attr: str) -> bytes:
|
|
91
|
+
return next(x for x in self.attributes if x.name == attr).read_bytes()
|
ekfsm/core/utils.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from anytree import AnyNode
|
|
5
|
+
|
|
6
|
+
from ekfsm.devices import CLASS_MAP
|
|
7
|
+
from ekfsm.exceptions import ConfigError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .components import HwModule
|
|
11
|
+
from ekfsm.devices.generic import Device
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BoardDictImporter:
|
|
15
|
+
|
|
16
|
+
def __init__(self, nodecls=AnyNode):
|
|
17
|
+
self.nodecls = nodecls
|
|
18
|
+
|
|
19
|
+
def import_(self, logger: logging.Logger, data, parent=None):
|
|
20
|
+
"""Import tree from `data`."""
|
|
21
|
+
return self.__import(logger, data, parent=parent)
|
|
22
|
+
|
|
23
|
+
def __import(self, logger: logging.Logger, data, parent=None):
|
|
24
|
+
from .components import HwModule
|
|
25
|
+
|
|
26
|
+
device_type = data.get("device_type")
|
|
27
|
+
nodecls = CLASS_MAP.get(device_type)
|
|
28
|
+
if nodecls is None:
|
|
29
|
+
raise ConfigError(f"Unknown device type: {device_type}")
|
|
30
|
+
|
|
31
|
+
children = data.pop("children", [])
|
|
32
|
+
if parent is not None and isinstance(parent, HwModule):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
node = nodecls(parent=parent, **data)
|
|
36
|
+
|
|
37
|
+
if children is not None:
|
|
38
|
+
for child in children:
|
|
39
|
+
try:
|
|
40
|
+
logger.debug(f"Importing sub device {child}")
|
|
41
|
+
self.__import(logger, child, parent=node)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(
|
|
44
|
+
f"Failed to import sub device {child}: {e}. continue anyway"
|
|
45
|
+
)
|
|
46
|
+
return node
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def deserialize_hardware_tree(
|
|
50
|
+
logger: logging.Logger, data: dict, parent: "HwModule"
|
|
51
|
+
) -> tuple[str, str, str, list["Device"]]:
|
|
52
|
+
importer = BoardDictImporter()
|
|
53
|
+
|
|
54
|
+
id = data.pop("id", None)
|
|
55
|
+
if id is None:
|
|
56
|
+
raise ConfigError("Board configuration must contain `id`")
|
|
57
|
+
name = data.pop("name", None)
|
|
58
|
+
if name is None:
|
|
59
|
+
raise ConfigError("Board configuration must contain `name`")
|
|
60
|
+
slot_type = data.pop("slot_type")
|
|
61
|
+
if slot_type is None:
|
|
62
|
+
raise ConfigError("Board configuration must contain `board_type`")
|
|
63
|
+
|
|
64
|
+
children = data.pop("children", None)
|
|
65
|
+
devices = []
|
|
66
|
+
if children:
|
|
67
|
+
for child in children:
|
|
68
|
+
try:
|
|
69
|
+
logger.debug(f"Importing top level device {child}")
|
|
70
|
+
node = importer.import_(logger, child, parent=parent)
|
|
71
|
+
devices.append(node)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(
|
|
74
|
+
f"Failed to import top level device {child}: {e}. continue anyway"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return id, name, slot_type, devices
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from ekfsm.devices.generic import Device
|
|
2
|
+
from ekfsm.devices.hwmon import HWMON
|
|
3
|
+
from ekfsm.devices.smbios import SMBIOS
|
|
4
|
+
from .eeprom import EEPROM, EKF_EEPROM, EKF_CCU_EEPROM
|
|
5
|
+
from .pmbus import PmBus
|
|
6
|
+
from .gpio import GPIO, EKFIdentificationIOExpander, GPIOExpander
|
|
7
|
+
from .ekf_sur_led import EKFSurLed
|
|
8
|
+
from .ekf_ccu_uc import EKFCcuUc
|
|
9
|
+
from .iio_thermal_humidity import IIOThermalHumidity
|
|
10
|
+
from .mux import I2CMux, MuxChannel
|
|
11
|
+
|
|
12
|
+
CLASS_MAP = {
|
|
13
|
+
"GenericDevice": Device,
|
|
14
|
+
"I2CMux": I2CMux,
|
|
15
|
+
"MuxChannel": MuxChannel,
|
|
16
|
+
"GPIO": GPIO,
|
|
17
|
+
"GPIOExpander": GPIOExpander,
|
|
18
|
+
"EKFIdentificationIOExpander": EKFIdentificationIOExpander,
|
|
19
|
+
"EEPROM": EEPROM,
|
|
20
|
+
"EKF_EEPROM": EKF_EEPROM,
|
|
21
|
+
"EKF_CCU_EEPROM": EKF_CCU_EEPROM,
|
|
22
|
+
"EKFCcuUc": EKFCcuUc,
|
|
23
|
+
"PmBus": PmBus,
|
|
24
|
+
"SMBIOS": SMBIOS,
|
|
25
|
+
"HWMON": HWMON,
|
|
26
|
+
"EKFSurLed": EKFSurLed,
|
|
27
|
+
"IIOThermalHumidity": IIOThermalHumidity,
|
|
28
|
+
}
|