Skip to content

Commit 47373eb

Browse files
committed
Initial FreeBSD support and detection improvements
1 parent 6e3b908 commit 47373eb

File tree

5 files changed

+185
-16
lines changed

5 files changed

+185
-16
lines changed

cros_ec_python/cros_ec.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
`cros_ec_python.devices.lpc.CrosEcLpc` and `cros_ec_python.devices.dev.CrosEcDev`.
66
"""
77

8-
import os
8+
import sys
99

1010
from .constants.COMMON import *
1111
from .baseclass import CrosEcClass
1212
from .devices import lpc
13-
if os.name == "posix":
13+
if sys.platform == "linux":
1414
from .devices import dev
1515
else:
1616
dev = None
@@ -36,10 +36,7 @@ def pick_device() -> DeviceTypes:
3636
* `DeviceTypes.LinuxDev` (see `cros_ec_python.devices.dev.CrosEcDev.detect()`)
3737
* `DeviceTypes.LPC` (see `cros_ec_python.devices.lpc.CrosEcLpc.detect()`)
3838
"""
39-
if os.name == "nt":
40-
# detect only works on Linux for now
41-
return DeviceTypes.LPC
42-
elif dev and dev.CrosEcDev.detect():
39+
if dev and dev.CrosEcDev.detect():
4340
return DeviceTypes.LinuxDev
4441
elif lpc and lpc.CrosEcLpc.detect():
4542
return DeviceTypes.LPC

cros_ec_python/devices/lpc.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import struct
22
import warnings
33
import errno
4+
import sys
5+
46
from ..baseclass import CrosEcClass
57
from ..constants.COMMON import *
68
from ..constants.LPC import *
@@ -42,14 +44,23 @@ def detect() -> bool:
4244
"""
4345
Checks for known CrOS EC memory map addresses in `/proc/ioports`.
4446
"""
45-
# TODO: Windows support (Get-CimInstance -Class Win32_PortResource)
46-
with open("/proc/ioports", "r") as f:
47-
for line in f:
48-
if line.lstrip()[:4] in (
49-
format(EC_LPC_ADDR_MEMMAP, "04x"),
50-
format(EC_LPC_ADDR_MEMMAP_FWAMD, "04x"),
51-
):
52-
return True
47+
if sys.platform == "linux":
48+
try:
49+
with open("/proc/ioports", "r") as f:
50+
for line in f:
51+
if line.lstrip()[:4] in (
52+
format(EC_LPC_ADDR_MEMMAP, "04x"),
53+
format(EC_LPC_ADDR_MEMMAP_FWAMD, "04x"),
54+
):
55+
return True
56+
return False
57+
except FileNotFoundError:
58+
pass
59+
60+
# Look for the memmap as a last resort
61+
return bool(
62+
CrosEcLpc.find_address(EC_LPC_ADDR_MEMMAP, EC_LPC_ADDR_MEMMAP_FWAMD)
63+
)
5364

5465
@staticmethod
5566
def find_address(*addresses, portio: PortIO | None = None) -> int | None:

cros_ec_python/ioports/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
# Manually pick WinRing0 (Windows)
3030
from cros_ec_python.ioports.winportio import WinPortIO
3131
portio = WinPortIO()
32+
33+
# Manually pick `/dev/io` (FreeBSD)
34+
from cros_ec_python.ioports.freebsdportio import FreeBsdPortIO
35+
portio = FreeBsdPortIO()
3236
```
3337
3438
**Reading from a port**

cros_ec_python/ioports/devportio.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
real downside to using this method.
77
"""
88

9+
from typing import IO
10+
911
from .baseportio import PortIOClass
1012

1113

1214
class DevPortIO(PortIOClass):
1315
"""
14-
A class to interact with the `/dev/port` device file.
16+
A class to interact with the `/dev/port` device file on Linux.
1517
"""
1618

17-
_dev_port = None
19+
_dev_port: IO | None = None
1820

1921
def __init__(self):
2022
"""
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""
2+
This file provides a way to interact with the `/dev/io` device file on
3+
FreeBSD.
4+
"""
5+
6+
from typing import Final, IO
7+
from fcntl import ioctl
8+
import struct
9+
10+
from .baseportio import PortIOClass
11+
12+
IODEV_PIO_READ: Final = 0
13+
IODEV_PIO_WRITE: Final = 1
14+
15+
16+
def _IOC(inout: int, group: str, num: int, length: int):
17+
"""
18+
Create an ioctl command number.
19+
Based on the FreeBSD kernels sys/sys/ioccom.h.
20+
"""
21+
IOCPARM_SHIFT: Final = 13 # number of bits for ioctl size
22+
IOCPARM_MASK: Final = ((1 << IOCPARM_SHIFT) - 1) # parameter length mask
23+
24+
return (((inout) | (((length) & IOCPARM_MASK) << 16) | (ord(group) << 8) | (num)))
25+
26+
27+
def _IOWR(group: str, num: int, length: int):
28+
"""
29+
Create an ioctl command number for read/write commands.
30+
Based on the FreeBSD kernels sys/sys/ioccom.h.
31+
"""
32+
IOC_VOID: Final = 0x20000000 # no parameters
33+
IOC_OUT: Final = 0x40000000 # copy out parameters
34+
IOC_IN: Final = 0x80000000 # copy in parameters
35+
IOC_INOUT: Final = (IOC_IN|IOC_OUT) # copy parameters in and out
36+
IOC_DIRMASK: Final = (IOC_VOID|IOC_OUT|IOC_IN) # mask for IN/OUT/VOID
37+
return _IOC(IOC_INOUT, group, num, length)
38+
39+
40+
def IODEV_PIO():
41+
"""
42+
Create an ioctl command number for the `/dev/io` device file.
43+
"""
44+
45+
# struct iodev_pio_req {
46+
# u_int access;
47+
# u_int port;
48+
# u_int width;
49+
# u_int val;
50+
# };
51+
52+
length = struct.calcsize("IIII")
53+
return _IOWR("I", 0, length)
54+
55+
56+
class FreeBsdPortIO(PortIOClass):
57+
"""
58+
A class to interact with the `/dev/io` device file on FreeBSD.
59+
"""
60+
61+
_dev_io: IO | None = None
62+
63+
def __init__(self):
64+
"""
65+
Initialize the `/dev/port` device file.
66+
"""
67+
self._dev_io = open("/dev/io", "wb", buffering=0)
68+
69+
def __del__(self):
70+
"""
71+
Close the `/dev/port` device file.
72+
"""
73+
if self._dev_io:
74+
self._dev_io.close()
75+
76+
def out_bytes(self, data: bytes, port: int) -> None:
77+
"""
78+
Write data to the specified port.
79+
:param data: Data to write.
80+
:param port: Port to write to.
81+
"""
82+
iodev_pio_req = struct.pack(
83+
"IIII", IODEV_PIO_WRITE, port, len(data), int.from_bytes(data, "little")
84+
)
85+
ioctl(self._dev_io, IODEV_PIO(), iodev_pio_req)
86+
87+
def outb(self, data: int, port: int) -> None:
88+
"""
89+
Write a byte (8 bit) to the specified port.
90+
:param data: Byte to write.
91+
:param port: Port to write to.
92+
"""
93+
self.out_bytes(data.to_bytes(1, "little"), port)
94+
95+
def outw(self, data: int, port: int) -> None:
96+
"""
97+
Write a word (16 bit) to the specified port.
98+
:param data: Word to write.
99+
:param port: Port to write to.
100+
"""
101+
self.out_bytes(data.to_bytes(2, "little"), port)
102+
103+
def outl(self, data: int, port: int) -> None:
104+
"""
105+
Write a long (32 bit) to the specified port.
106+
:param data: Long to write.
107+
:param port: Port to write to.
108+
"""
109+
self.out_bytes(data.to_bytes(4, "little"), port)
110+
111+
def in_bytes(self, port: int, num: int) -> bytes:
112+
"""
113+
Read data from the specified port.
114+
:param port: Port to read from.
115+
:param num: Number of bytes to read (1 - 4).
116+
:return: Data read.
117+
"""
118+
iodev_pio_req = struct.pack("IIII", IODEV_PIO_READ, port, num, 0)
119+
return ioctl(self._dev_io, IODEV_PIO(), iodev_pio_req)[struct.calcsize("III") :]
120+
121+
def inb(self, port: int) -> int:
122+
"""
123+
Read a byte (8 bit) from the specified port.
124+
:param port: Port to read from.
125+
:return: Byte read.
126+
"""
127+
return int.from_bytes(self.in_bytes(port, 1), "little")
128+
129+
def inw(self, port: int) -> int:
130+
"""
131+
Read a word (16 bit) from the specified port.
132+
:param port: Port to read from.
133+
:return: Word read.
134+
"""
135+
return int.from_bytes(self.in_bytes(port, 2), "little")
136+
137+
def inl(self, port: int) -> int:
138+
"""
139+
Read a long (32 bit) from the specified port.
140+
:param port: Port to read from.
141+
:return: Long read.
142+
"""
143+
return int.from_bytes(self.in_bytes(port, 4), "little")
144+
145+
def ioperm(self, port: int, num: int, turn_on: bool) -> None:
146+
"""
147+
`ioperm` stub function. The iopl will already be raised from opening `/dev/io` and is not required.
148+
"""
149+
pass
150+
151+
def iopl(self, level: int) -> None:
152+
"""
153+
`iopl` stub function. The iopl will already be raised from opening `/dev/io` and is not required.
154+
"""
155+
pass

0 commit comments

Comments
 (0)