はじめに
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
初期化メソッド
デバイス名による初期化
初期化時にデバイス名(例えば 'uio0'
や 'uio1'
)を指定します。オプションでレジスタ空間の大きさをバイト数で指定できます。省略時は 0x1000(4KByte)です。初期化時に指定されたデバイス名のデバイスドライバを open し、指定された範囲を mmap.mmap でマッピングします。
class Uio:
"""A simple uio class"""
def __init__(self, name, length=0x1000):
self.name = name
self.device_name = '/dev/%s' % self.name
self.device_file = os.open(self.device_name, os.O_RDWR | os.O_SYNC)
self.length = length
self.memmap = mmap.mmap(self.device_file,
self.length,
mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE,
offset=0)
分かりやすい名前による初期化
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, length=0x1000):
self.name = name
self.device_name = '/dev/%s' % Uio.find_device_file(self.name)
self.device_file = os.open(self.device_name, os.O_RDWR | os.O_SYNC)
self.length = length
self.memmap = mmap.mmap(self.device_file,
self.length,
mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE,
offset=0)
割り込み関連のメソッド
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 のデバイスレジスタをアクセスするための Uio.Regs
クラスのインスタンスを生成します。Uio.Regs
に関しては次節を参照してください。
インスタンスを生成する際、レジスタの開始アドレスとレジスタ空間の大きさを指定することが出来ます。ここで指定するレジスタの開始アドレスは、UIO で設定された物理アドレスからのオフセット値です。
def regs(self, offset=0, length=None):
if length == None:
length = self.length
if offset+length > self.length:
raise ValueError("region range error")
return Uio.Regs(self.memmap, offset, 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.word_array = np.frombuffer(self.memmap, np.uint32, self.length>>2, self.offset)
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)))