1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
#
# Copyright 2017 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Access to UIO mapped memory.
"""
import os
from contextlib import contextmanager
from builtins import object
import pyudev
import usrp_mpm.libpyusrp_periphs as lib
from usrp_mpm.mpmlog import get_logger
UIO_SYSFS_BASE_DIR = '/sys/class/uio'
UIO_DEV_BASE_DIR = '/dev'
@contextmanager
def open_uio(label=None, path=None, length=None, read_only=True, offset=None):
"""Convenience function for creating a UIO object.
Use this like you would open() for a file"""
uio_obj = UIO(label, path, length, read_only, offset)
uio_obj._open()
try:
yield uio_obj
finally:
uio_obj._close()
def get_all_uio_devs():
"""
Return a list of all uio devices. Will look something like
['uio0', 'uio1', ...].
"""
try:
context = pyudev.Context()
paths = [os.path.split(device.device_node)[-1]
for device in context.list_devices(subsystem="uio")]
return paths
except OSError:
# Typically means UIO devices
return []
def get_uio_map_info(uio_dev, map_num):
"""
Returns all the map info for a given UIO device and map number.
Example: If uio_dev is 'uio0', and map_num is 0, it will list all files
in /sys/class/uio/uio0/maps/map0/ and create a dictionary with filenames
as keys and content as value.
Numbers are casted to numbers automatically. Strings remain strings.
"""
map_info = {}
map_info_path = os.path.join(
UIO_SYSFS_BASE_DIR, uio_dev, 'maps', 'map{0}'.format(map_num)
)
for info_file in os.listdir(map_info_path):
map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip()
try:
map_info[info_file] = int(map_info_value, 0)
except ValueError:
map_info[info_file] = map_info_value
return map_info
def find_uio_device(label, logger=None):
"""
Given a label, returns a tuple (uio_device, map_info).
uio_device is something like '/dev/uio0'. map_info is a dictionary with
information regarding the UIO device read from the map info sysfs dir.
Note: We assume a single map (map0) for all UIO devices here.
"""
uio_devices = get_all_uio_devs()
if logger:
logger.trace("Found the following UIO devices: `{0}'".format(','.join(uio_devices)))
for uio_device in uio_devices:
map0_info = get_uio_map_info(uio_device, 0)
if logger:
logger.trace("{0} has map info: {1}".format(uio_device, map0_info))
if map0_info.get('name') == label:
if logger:
logger.trace("Device matches label: `{0}'".format(uio_device))
return os.path.join(UIO_DEV_BASE_DIR, uio_device), map0_info
if logger:
logger.warning("Found no matching UIO device for label `{0}'".format(label))
return None, None
class UIO(object):
"""
Provides peek/poke interfaces for uio-mapped memory.
This object will not, by default, open the associated UIO device. To
actually open the device, you have two options:
- Use the instantiation of this class as a context manager (using a `with`
statement), like this:
>>> uio0 = UIO(path="/dev/uio0"):
>>> with uio0:
>>> uio0.peek32(addr)
>>> uio0.poke32(addr, value)
Or like this:
>>> with UIO(path="/dev/uio0") as uio0:
>>> uio0.peek32(addr)
>>> uio0.poke32(addr, value)
- This is Highly Discouraged, but if you need to, manually call _open() and _close():
>>> uio0 = UIO(path="/dev/uio0")
>>> uio0._open()
>>> uio0.peek32(addr)
>>> uio0.poke32(addr, value)
>>> uio0._close()
Arguments:
label -- Label of the UIO device. The label is set in the device tree
overlay
path -- Path to UIO device, e.g. '/dev/uio0'. This is ignored if 'label' is
provided.
length -- Number of bytes in the address space (is passed to mmap.mmap).
This is usually automatically determined. No need to set it.
Unless you really know what you're doing.
read_only -- Boolean; True == ro, False == rw
offset -- Passed to mmap.mmap.
This is usually automatically determined. No need to set it.
Unless you really know what you're doing.
"""
def __init__(self, label=None, path=None, length=None, read_only=True, offset=None):
self.log = get_logger('UIO')
if label is None:
self._path = path
self.log.trace("Using UIO device `{0}'".format(path))
uio_device = os.path.split(path)[-1]
self.log.trace("Getting map info for UIO device `{0}'".format(uio_device))
map_info = get_uio_map_info(uio_device, 0)
# Python can't tell the size of a uio device by itself
assert length is not None
else:
self.log.trace("Using UIO device by label `{0}'".format(label))
self._path, map_info = find_uio_device(label, self.log)
if self._path is None or map_info is None:
self.log.error("Could not find a UIO device for label {0}".format(label))
raise RuntimeError("Could not find a UIO device for label {0}".format(label))
# TODO If we ever support multiple maps, check if this is correct...
offset = offset or map_info['offset']
assert offset == 0 # ...and then remove this line
length = length or map_info['size']
self.log.trace("UIO device is being opened read-{0}.".format(
"only" if read_only else "write"))
self._read_only = read_only
# Our UIO objects are managed in C++ land, which gives us more granular control over
# opening and closing
self._uio = lib.types.mmap_regs_iface(self._path, length, offset, self._read_only, False)
# Reference counter for safely __enter__ and __exit__-ing
self._ref_count = 0
def __enter__(self):
return self._open()
def __exit__(self, exc_type, exc_value, traceback):
self._close()
return exc_type is None
def _open(self):
"""Actually open the UIO device.
You need to call this before doing peeks and pokes. See also close().
If you're using the UIO object as a context manager, it will open the
file automatically.
"""
if self._ref_count == 0:
self._uio.open()
self._ref_count += 1
return self
def _close(self):
"""Close a UIO device.
UIO devices can be problematic with regards to file descriptor leakage,
so it is recommended to close a UIO device when it is no longer needed.
"""
self._ref_count -= 1
if self._ref_count == 0:
self._uio.close()
def peek32(self, addr):
"""
Returns the 32-bit value starting at address addr as an integer
"""
return self._uio.peek32(addr)
def poke32(self, addr, val):
"""
Writes the 32-bit value val to address starting at addr.
Will throw if read_only was set to True.
A value that exceeds 32 bits will be truncated to 32 bits.
"""
assert not self._read_only
return self._uio.poke32(addr, val)
|