Skip to content

Commit aba6556

Browse files
committed
unix-ffi/os.linux: Add Linux-specific system call wrappers.
New package providing ioctl helpers, mount/umount/reboot, execv/execvp, setenv, realpath, and device access modules for MTD, SPI, I2C, evdev, block devices, syslog and monotonic clocks via FFI. Made-with: Cursor
1 parent fd05cde commit aba6556

10 files changed

Lines changed: 389 additions & 0 deletions

File tree

unix-ffi/os.linux/manifest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
metadata(version="0.1.0", description="Linux-specific system call wrappers (ioctl, mount, reboot, MTD, SPI, I2C, evdev, syslog, clock_gettime).")
2+
3+
require("ffilib")
4+
require("os")
5+
6+
package("os/linux")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
import ffilib
3+
import os
4+
5+
6+
libc = ffilib.libc()
7+
sleep = libc.func('I', 'sleep', 'I')
8+
_mount = libc.func('i', 'mount', 'sssLs')
9+
_umount = libc.func('i', 'umount', 's')
10+
_setenv = libc.func('i', 'setenv', 'ssi')
11+
_reboot_syscall = libc.func('l', 'syscall', 'liiis')
12+
_realpath = libc.func("s", "realpath", "ss")
13+
_free = libc.func("v", "free", "p")
14+
15+
16+
17+
LINUX_REBOOT_MAGIC1 = 0xfee1dead
18+
LINUX_REBOOT_MAGIC2 = 672274793
19+
LINUX_REBOOT_CMD_RESTART = 0x01234567
20+
LINUX_REBOOT_CMD_HALT = 0xCDEF0123
21+
LINUX_REBOOT_CMD_CAD_ON = 0x89ABCDEF
22+
LINUX_REBOOT_CMD_CAD_OFF = 0x00000000
23+
LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC
24+
LINUX_REBOOT_CMD_RESTART2 = 0xA1B2C3D4
25+
LINUX_REBOOT_CMD_SW_SUSPEND = 0xD000FCE2
26+
LINUX_REBOOT_CMD_KEXEC = 0x45584543
27+
28+
29+
def reboot(cmd, arg_str):
30+
SYS_reboot = 142
31+
e = _reboot_syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg_str)
32+
os.check_error(e)
33+
34+
35+
def mount(source, target, fstype, flags = 0, opts = None):
36+
e = _mount(source, target, fstype, flags, opts)
37+
os.check_error(e)
38+
39+
40+
def umount(target):
41+
e = _umount(target)
42+
os.check_error(e)
43+
44+
45+
def execv(path, args = []):
46+
assert args, '`args` argument cannot be empty'
47+
_args = [path] + args + [None]
48+
_execl = libc.func('i', 'execl', 's'*len(_args))
49+
e = _execl(*_args)
50+
os.check_error(e)
51+
52+
53+
def execvp(executable, args = []):
54+
assert args, '`args` argument cannot be empty'
55+
_args = [executable] + args + [None]
56+
_execlp = libc.func('i', 'execlp', 's'*len(_args))
57+
e = _execlp(*_args)
58+
os.check_error(e)
59+
60+
61+
def setenv(name, value, overwrite = True):
62+
e = _setenv(name, value, 1 if overwrite else 0)
63+
os.check_error(e)
64+
65+
def realpath(path):
66+
p = _realpath(path, None)
67+
# Do not free the string here, it is tracked by upy and freed by it.
68+
# _free(p)
69+
if p == None:
70+
os.raise_error()
71+
return p
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
# buffer data from src_fobj (it could be the stdin pipe) before writing
3+
# it out in blocksz bytes chunks
4+
def block_copy(src_fobj, dst_fobj, byte_count = 0, blocksz=512, progress_func=None):
5+
buf = bytearray(blocksz)
6+
buf_view = memoryview(buf)
7+
read_bytes = 0
8+
left_to_read = byte_count
9+
total_written_bytes = 0
10+
while True:
11+
sz = src_fobj.readinto(buf_view[read_bytes:])
12+
#print('readinto() -> {}, {}'.format(read_bytes, sz), file=sys.stderr)
13+
# in blocking mode sz will be zero only on EOF
14+
left_to_read -= sz
15+
read_bytes += sz
16+
if not sz or (byte_count != 0 and left_to_read <= 0):
17+
cnt = read_bytes
18+
if byte_count != 0 and left_to_read <= 0:
19+
cnt += left_to_read
20+
dst_fobj.write(buf_view[:cnt])
21+
total_written_bytes += cnt
22+
if progress_func is not None:
23+
progress_func(total_written_bytes)
24+
break
25+
if read_bytes == blocksz:
26+
dst_fobj.write(buf_view)
27+
#print('write({})'.format(read_bytes), file=sys.stderr)
28+
total_written_bytes += read_bytes
29+
read_bytes = 0
30+
if progress_func is not None:
31+
progress_func(total_written_bytes)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
import builtins
3+
import os
4+
from os.linux import ioctl
5+
6+
DEV_PATH_TPL = '/dev/input/event{}'
7+
8+
KEY_SELECT = 0x161
9+
10+
def EVIOCGKEY(len):
11+
return ioctl._ioc(ioctl._IOC_READ, ord('E'), 0x18, len)
12+
13+
def get_global_keystate(dev, byte_array):
14+
r = ioctl.ioctl_p(dev.fileno(), EVIOCGKEY(len(byte_array)), byte_array)
15+
os.check_error(r)
16+
17+
def open(index=0):
18+
return builtins.open(DEV_PATH_TPL.format(index), 'r+b')

unix-ffi/os.linux/os/linux/i2c.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
import builtins
3+
from os.linux import ioctl
4+
5+
DEV_PATH_TPL = '/dev/i2c-{}'
6+
7+
I2C_SLAVE = 0x0703
8+
9+
def set_slave_addr(dev_fd, addr):
10+
ioctl.ioctl_l(dev_fd, I2C_SLAVE, addr)
11+
12+
def open(index=0):
13+
return builtins.open(DEV_PATH_TPL.format(index), 'r+b')
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
import ffilib
3+
4+
libc = ffilib.libc()
5+
6+
TIOCCONS = 0x541D
7+
8+
_IOC_WRITE = 1
9+
_IOC_READ = 2
10+
11+
_IOC_NRBITS = 8
12+
_IOC_TYPEBITS = 8
13+
_IOC_SIZEBITS = 14
14+
_IOC_DIRBITS = 2
15+
16+
_IOC_NRSHIFT = 0
17+
_IOC_TYPESHIFT = (_IOC_NRSHIFT+_IOC_NRBITS)
18+
_IOC_SIZESHIFT = (_IOC_TYPESHIFT+_IOC_TYPEBITS)
19+
_IOC_DIRSHIFT = (_IOC_SIZESHIFT+_IOC_SIZEBITS)
20+
21+
def _ioc(dir, type, num, size):
22+
return ((num << _IOC_NRSHIFT) | (type << _IOC_TYPESHIFT) |
23+
(size << _IOC_SIZESHIFT) | (dir << _IOC_DIRSHIFT))
24+
25+
ioctl_p = libc.func("i", "ioctl", "iip")
26+
ioctl_l = libc.func("i", "ioctl", "iil")
27+
del libc

unix-ffi/os.linux/os/linux/mtd.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
import os
3+
from os.linux import ioctl
4+
try:
5+
import ustruct as struct
6+
except:
7+
import struct
8+
9+
10+
# struct erase_info_user {
11+
# __u32 start;
12+
# __u32 length;
13+
# };
14+
15+
MTD_NORFLASH = 3
16+
17+
def _mtd_ioc(dir, num, size):
18+
return ioctl._ioc(dir, ord('M'), num, size)
19+
20+
MEM_INFO_STRUCT = 'BIIIIIII'
21+
_MEM_INFO_LEN = struct.calcsize(MEM_INFO_STRUCT)
22+
ERASE_INFO_STRUCT = 'II'
23+
_ERASE_INFO_LEN = struct.calcsize(ERASE_INFO_STRUCT)
24+
MEMGETINFO = _mtd_ioc(ioctl._IOC_READ, 1, _MEM_INFO_LEN)
25+
MEMERASE = _mtd_ioc(ioctl._IOC_WRITE, 2, _ERASE_INFO_LEN)
26+
MEMUNLOCK = _mtd_ioc(ioctl._IOC_WRITE, 6, _ERASE_INFO_LEN)
27+
28+
29+
def _pack_erase_info(start, length):
30+
return struct.pack(ERASE_INFO_STRUCT, start, length)
31+
32+
33+
def _check_offset(offset, eblock):
34+
assert offset % eblock == 0, \
35+
'offset {} is not a multiple of erase block {}'.format(offset, eblock)
36+
37+
38+
def info(mtd_dev):
39+
mem_info = bytearray(_MEM_INFO_LEN)
40+
e = ioctl.ioctl_p(mtd_dev.fileno(), MEMGETINFO, mem_info)
41+
os.check_error(e)
42+
# do not return padding words
43+
return struct.unpack(MEM_INFO_STRUCT, mem_info)[:-2]
44+
45+
46+
def unlock(mtd_dev, offset, eblock_size):
47+
_check_offset(offset, eblock_size)
48+
erase_info_user = _pack_erase_info(offset, eblock_size)
49+
e = ioctl.ioctl_p(mtd_dev.fileno(), MEMUNLOCK, erase_info_user)
50+
os.check_error(e)
51+
52+
53+
def erase(mtd_dev, offset, eblock_size):
54+
_check_offset(offset, eblock_size)
55+
erase_info_user = _pack_erase_info(offset, eblock_size)
56+
e = ioctl.ioctl_p(mtd_dev.fileno(), MEMERASE, erase_info_user)
57+
os.check_error(e)
58+
59+
60+
def read(mtd_dev, offset, length):
61+
assert mtd_dev.seek(offset) == offset
62+
return mtd_dev.read(length)
63+
64+
65+
def write(mtd_dev, offset, data):
66+
assert mtd_dev.seek(offset) == offset
67+
return mtd_dev.write(data)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import builtins
3+
from os.linux import ioctl
4+
5+
DEV_PATH_TPL = '/dev/spidev{}.{}'
6+
7+
def _spidev_ioc(dir, num, size):
8+
return ioctl._ioc(dir, ord('k'), num, size)
9+
10+
SPI_IOC_RD_BITS_PER_WORD = _spidev_ioc(ioctl._IOC_READ, 3, 1)
11+
SPI_IOC_WR_BITS_PER_WORD = _spidev_ioc(ioctl._IOC_WRITE, 3, 1)
12+
13+
SPI_IOC_RD_MAX_SPEED_HZ = _spidev_ioc(ioctl._IOC_READ, 4, 4)
14+
SPI_IOC_WR_MAX_SPEED_HZ = _spidev_ioc(ioctl._IOC_WRITE, 4, 4)
15+
16+
def open(bus_idx=0, cs_idx=0):
17+
return builtins.open(DEV_PATH_TPL.format(bus_idx, cs_idx), 'r+b')
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
2+
import sys
3+
import os
4+
from os import libc
5+
6+
openlog_ = libc.func("v", "openlog", "sii")
7+
setlogmask_ = libc.func("i", "setlogmask", "i")
8+
syslog_ = libc.func("v", "syslog", "is")
9+
isatty_ = libc.func("i", "isatty", "i")
10+
11+
# Syslog priorities
12+
CRITICAL = 2
13+
ERROR = 3
14+
WARNING = 4
15+
NOTICE = 5
16+
INFO = 6
17+
DEBUG = 7
18+
NOTSET = 0
19+
20+
# Facility codes
21+
LOG_USER = (1<<3) # random user-level messages
22+
23+
# Option flags for openlog
24+
LOG_PID = 0x01 # log the pid with each message
25+
LOG_CONS = 0x02 # log on the console if errors in sending
26+
LOG_ODELAY = 0x04 # delay open until first syslog() (default)
27+
LOG_NDELAY = 0x08 # don't delay open
28+
LOG_NOWAIT = 0x10 # don't wait for console forks: DEPRECATED
29+
LOG_PERROR = 0x20 # log to stderr as well
30+
31+
def _logmask_upto(pri):
32+
return ((1<<((pri)+1))-1)
33+
34+
class Logger:
35+
36+
def __init__(self, name):
37+
self.name = name
38+
39+
def log(self, level, msg, *args):
40+
if self.name is not None:
41+
s = ('%s:'+ msg) % ((self.name,) + args)
42+
else:
43+
s = msg % args
44+
syslog_(level, s)
45+
46+
def debug(self, msg, *args):
47+
self.log(DEBUG, msg, *args)
48+
49+
def info(self, msg, *args):
50+
self.log(INFO, msg, *args)
51+
52+
def warning(self, msg, *args):
53+
self.log(WARNING, msg, *args)
54+
55+
def error(self, msg, *args):
56+
self.log(ERROR, msg, *args)
57+
58+
def critical(self, msg, *args):
59+
self.log(CRITICAL, msg, *args)
60+
61+
62+
_level = ERROR
63+
_loggers = {}
64+
65+
r = isatty_(sys.stdout.fileno())
66+
os.check_error(r)
67+
68+
flags = LOG_CONS | LOG_PID
69+
# if we are outputting to a tty log also to stderr
70+
if r > 0:
71+
flags |= LOG_PERROR
72+
ident = 'python'
73+
if len(sys.argv):
74+
ident = sys.argv[0]
75+
openlog_(ident, flags, LOG_USER)
76+
77+
r = setlogmask_(_logmask_upto(_level))
78+
os.check_error(r)
79+
80+
def getLogger(name):
81+
if name in _loggers:
82+
return _loggers[name]
83+
l = Logger(name)
84+
_loggers[name] = l
85+
return l
86+
87+
def info(msg, *args):
88+
getLogger(None).info(msg, *args)
89+
90+
def debug(msg, *args):
91+
getLogger(None).debug(msg, *args)
92+
93+
def basicConfig(level=INFO, filename=None, format=None):
94+
global _level
95+
_level = level
96+
if filename is not None:
97+
print("logging.basicConfig: filename arg is not supported")
98+
if format is not None:
99+
print("logging.basicConfig: format arg is not supported")

unix-ffi/os.linux/os/linux/time.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
import ustruct
3+
import ffilib
4+
import os
5+
6+
7+
TIMESPEC_FMT = 'll'
8+
TIMESPEC_SIZE = ustruct.calcsize(TIMESPEC_FMT)
9+
TIMEVAL_FMT = TIMESPEC_FMT
10+
TIMEVAL_SIZE = ustruct.calcsize(TIMEVAL_FMT)
11+
12+
CLOCK_MONOTONIC = 1
13+
CLOCK_MONOTONIC_RAW = 4
14+
15+
librt = ffilib.open('librt')
16+
_clock_gettime = librt.func('i', 'clock_gettime', 'ip')
17+
_gettimeofday = librt.func('i', 'gettimeofday', 'ip')
18+
_ts_buf = bytearray(TIMESPEC_SIZE)
19+
_tv_buf = bytearray(TIMEVAL_SIZE)
20+
21+
def clock_gettimeofday(clk_id):
22+
e1 = _clock_gettime(clk_id, _ts_buf)
23+
e2 = _gettimeofday(_tv_buf, None)
24+
os.check_error(e1)
25+
os.check_error(e2)
26+
s, ns = ustruct.unpack(TIMESPEC_FMT, _ts_buf)
27+
utc_s, utc_us = ustruct.unpack(TIMEVAL_FMT, _tv_buf)
28+
return s, ns, utc_s, utc_us
29+
30+
def clock_gettime(clk_id):
31+
e = _clock_gettime(clk_id, _ts_buf)
32+
os.check_error(e)
33+
s, ns = ustruct.unpack(TIMESPEC_FMT, _ts_buf)
34+
return (s*10**9)+ns
35+
36+
def monotime():
37+
return clock_gettime(CLOCK_MONOTONIC)
38+
39+
def monotime_raw():
40+
return clock_gettime(CLOCK_MONOTONIC_RAW)

0 commit comments

Comments
 (0)