はじめに
UIO とは
UIO はユーザー空間でデバイスドライバを作成する仕組みです。
ユーザー空間で UIO を利用する際は、/dev/uio0 を open して mmap すると、デバイスのレジスタ空間が見えます。この記事では、Python と numpy を使って UIO のデバイスレジスタをアクセスする方法を示します。
Uio クラス
必要なライブラリをインポート
Uio クラスでは numpy、mmap、os、glob、re パッケージを使うのでインポートします。
import numpy as np
import mmap
import os
import glob
import re
初期化メソッド
uio は、通常、'uio0'
や 'uio1'
などの uio + デバイス番号 みたいなのがデバイス名になります。しかし uio が多くなると、このようなデバイス名では使いたい uio デバイスを特定するのが困難になります。通常 uio には、'uio0'
などのデバイス名の他に、デバイスツリーなどで指定した名前を持っています。
例えば次のデバイスツリーで uio を作成すると、
/dts-v1/; /plugin/;
/ {
fragment@0 {
target-path = "/amba_pl@0";
#address-cells = <2>;
#size-cells = <2>;
__overlay__ {
#address-cells = <2>;
#size-cells = <2>;
negative-uio {
compatible = "generic-uio";
reg = <0x0 0xA0010000 0x0 0x10000>;
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
};
};
} ;
} ;
/sys/class/uio/uioX/name (uioのデバイス番号Xは任意の整数)に デバイスツリーのノード名(この例では negative-uio)がセットされます。
shell$ cat /sys/class/uio/uio4/name
negative-uio
shell$
そこで、初期化時には名前を指定して、その名前を持つ uio を検索して初期しています。
class Uio:
"""A simple uio class"""
@staticmethod
def find_device_file(device_name):
device_file = None
r = re.compile("/sys/class/uio/(.*)/name")
for uio_device_name_file in glob.glob("/sys/class/uio/uio*/name"):
f = open(uio_device_name_file, "r")
uio_device_name = f.readline().strip()
if uio_device_name == device_name:
m = r.match(uio_device_name_file)
device_file = m.group(1)
f.close()
return device_file
def __init__(self, name):
self.name = name
self.device_name = Uio.find_device_file(self.name)
self.device_file = os.open('/dev/%s' % self.device_name, os.O_RDWR | os.O_SYNC)
self.memmap_dict = {}
割り込み関連のメソッド
UIO にはデバイスドライバに直接ライト/リードすることで割り込みを制御出来ます。詳しくは次の記事を参照してください。
def irq_on(self):
os.write(self.device_file, bytes([1, 0, 0, 0]))
def irq_off(self):
os.write(self.device_file, bytes([0, 0, 0, 0]))
def wait_irq(self):
os.read(self.device_file, 4)
マッピング情報関連のメソッド
UIO が reg プロパティで指定するレジスタ空間(アドレスとサイズの組)は複数指定することが出来ます。
例えば次のデバイスツリーで UIO を作成すると、この UIO は二つのレジスタ空間を持っています。
#address-cells = <2>;
#size-cells = <2>;
uio_sample {
compatible = "generic-uio";
reg = <0x04 0x00000000 0x0 0x00010000
0x04 0x00010000 0x0 0x00010000>;
};
これらのレジスタ空間のマッピング情報は /sys/class/uio/uioX/maps/ (uioのデバイス番号Xは任意の整数) を読むことで得られます。
shell$ cat /sys/class/uio/uioX/maps/map0/addr
0x400000000
shell$ cat /sys/class/uio/uioX/maps/map0/size
0x10000
shell$ cat /sys/class/uio/uioX/maps/map1/addr
0x400010000
shell$ cat /sys/class/uio/uioX/maps/map1/size
0x10000
そこで、これらのレジスタ空間のマッピング情報を得る関数を定義します。
def sys_class_file_name(self, name):
return '/sys/class/uio/%s/%s' % (self.device_name, name)
def read_class_integer(self, name):
file_name = self.sys_class_file_name(name)
with open(file_name, "r") as file:
value = file.readline().strip()
if value.startswith("0x") or value.startswith("0X"):
return int(value, 16)
elif value.isdigit():
return int(value, 10)
raise ValueError("Invalid value %s in %s " % (value, file_name))
def get_map_addr(self, index=0):
return self.read_class_integer('maps/map%d/addr' % index)
def get_map_size(self, index=0):
return self.read_class_integer('maps/map%d/size' % index)
def get_map_offset(self, index=0):
return self.read_class_integer('maps/map%d/offset' % index)
def get_map_info(self, index=0):
map_addr = self.get_map_addr(index)
map_size = self.get_map_size(index)
map_offset = self.get_map_offset(index)
return dict(addr=map_addr, size=map_size, offset=map_offset)
レジスタアクセス用のオブジェクトを生成
UIO のデバイスレジスタをアクセスするための Uio.Regs
クラスのインスタンスを生成します。Uio.Regs
に関しては次節を参照してください。
インスタンスを生成する際、前節で説明した複数のレジスタ空間を持つ場合にどのレジスタ空間かを指定する index 、マッピングするレジスタ空間の開始アドレスを指定する offset、マッピングするレジスタ空間の大きさを指定する length を指定することが出来ます。
レジスタ空間をユーザーからアクセス出来るように memmap() を使ってユーザー空間にマッピングします。
ここで少々面倒なのが、マッピングする際のサイズのアライメントです。memmap() で指定するサイズは、ページ単位で割り切れなければなりません。
また、memmap() を使ってユーザー空間にマッピングされた先頭アドレスはページ単位でアライメントされています。もともとのレジスタ空間の開始アドレスがページ単位でアライメントされていない場合には、その分補正が必要です。
UIO の仕様では、memmap() で指定するオフセットは、複数のレジスタ空間を持つ場合にどのレジスタ空間かを指定する index にページサイズを乗じた値を指定します。これにより、どのレジスタ空間をマッピングするかを特定します。
def regs(self, index=0, offset=0, length=None):
if index in self.memmap_dict.keys():
memmap_info = self.memmap_dict[index]
memmap = memmap_info['memmap']
mmap_offset = memmap_info['offset']
mmap_size = memmap_info['size']
mmap_addr = memmap_info['addr']
else:
page_size = os.sysconf("SC_PAGE_SIZE")
map_info = self.get_map_info(index)
mmap_addr = map_info['addr']
mmap_offset = ((map_info['addr'] + map_info['offset']) ) % page_size
mmap_size = ((map_info['size'] + page_size - 1) // page_size) * page_size
memmap = mmap.mmap(self.device_file,
mmap_size,
mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE,
index*page_size)
memmap_info = {'memmap': memmap, 'size': mmap_size, 'addr': mmap_addr, 'offset': mmap_offset}
self.memmap_dict[index] = memmap_info
regs_offset = mmap_offset + offset
if length == None:
regs_length = mmap_size - regs_offset
elif regs_offset + length <= mmap_size:
regs_length = length
else:
raise ValueError("region range error")
return Uio.Regs(memmap, regs_offset, regs_length)
Uio.Regs クラス
mmap.mmap() でユーザー空間にマッピングしているレジスタ空間を numpy の frombuffer() を使って numpy の配列として見えるようにします。レジスタへのアクセスは、ここで作った numpy の配列にアクセスすることで行います。
class Regs:
def __init__(self, memmap, offset, length):
self.memmap = memmap
self.offset = offset
self.length = length
self.alloc_word_array()
self.alloc_byte_array()
def alloc_word_array(self):
self.word_array = np.frombuffer(self.memmap, np.uint32, self.length>>2, self.offset)
def alloc_byte_array(self):
self.byte_array = np.frombuffer(self.memmap, np.uint8 , self.length>>0, self.offset)
def read_word(self, offset):
return int(self.word_array[offset>>2])
def read_byte(self, offset):
return int(self.byte_array[offset>>0])
def write_word(self, offset, data):
self.word_array[offset>>2] = np.uint32(data)
def write_byte(self, offset, data):
self.byte_array[offset>>0] = np.uint8(data)
補足(注意事項)
レジスタのアクセスの際は、この例のように numpy の配列を使ってください。
Python の mmap オブジェクトの read/write メソッドは使わないでください。
詳細は次の記事を参照してください。
*『Python の mmap.read メソッドを使って arm64 の非キャッシュ領域をアクセスするときの挙動について』@Qiita
使用例
from uio import Uio
class Sample:
START_REGS = 0x00
ARG0_REGS = 0x04
ARG1_REGS = 0x08
RETURN_REGS = 0x0C
def __init__(self)
self.uio = Uio('negative-uio')
self.regs = self.uio.regs()
def func(self, arg0, arg1)
self.regs.write_word(Sample.ARG0_REGS , arg0)
self.regs.write_word(Sample.ARG1_REGS , arg1)
self.regs.write_byte(Sample.START_REGS, 1)
self.uio.irq_on()
self.uio.wait_irq()
return self.regs.read_word(Sample.RETURN_REGS)
if __name__ == '__main__':
sample = Sample()
for x in range (0,9):
for y in range (0,9):
print ("func({0},{1}) => {2}".format(x, y, sample.func(x,y)))