0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python と mmapio モジュール で UIO を制御

Last updated at Posted at 2025-01-27

はじめに

UIO とは

UIO はユーザー空間でデバイスドライバを作成する仕組みです。

ユーザー空間で UIO を利用する際は、/dev/uio0 を open して mmap すると、デバイスのレジスタ空間が見えます。

mmapio モジュールとは

mmapio モジュールは低レベルな I/O アクセスを行うための C で記述した Python モジュールです。詳細は以下の記事を参考にしてください。

この記事で紹介する mmapio モジュールと uio モジュールは以下の URL で公開しています。

この記事では、mmapio モジュールを使って UIO のデバイスレジスタをアクセスする方法を示します。

Uio クラス

python-mmapio は以下のようなディレクトリの構成になっています。Python パッケージの名前は mmapio です。

  • setup.py
  • mmapio/
    • __init__.py
    • mmapio.c
    • uio.py

Uio クラスは uio モジュール(uio.py)にあります。この章では Uio クラスについて説明します。

必要なライブラリをインポート

Uio クラスでは mmap、os、glob、re パッケージを使うのでインポートします。また、mmapio モジュールから MemoryMappedIO クラスをインポートします。

mmapio/uio.py
import mmap
import os
import glob
import re
from .mmapio import MemoryMappedIO

初期化メソッド

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 を検索して初期しています。

uio.py
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 にはデバイスドライバに直接ライト/リードすることで割り込みを制御出来ます。詳しくは次の記事を参照してください。

uio.py
    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

そこで、これらのレジスタ空間のマッピング情報を得る関数を定義します。

uio.py
    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 のデバイスレジスタをアクセスするための MemoryMappedIO クラスのインスタンスを生成します。MemoryMappedIO に関しては『メモリマップド I/O レジスタにアクセスする Python モジュール を C で記述』を参照してください。

インスタンスを生成する際、前節で説明した複数のレジスタ空間を持つ場合にどのレジスタ空間かを指定する index 、マッピングするレジスタ空間の開始アドレスを指定する offset、マッピングするレジスタ空間の大きさを指定する length を指定することが出来ます。

レジスタ空間をユーザーからアクセス出来るように memmap() を使ってユーザー空間にマッピングします。
ここで少々面倒なのが、マッピングする際のサイズのアライメントです。memmap() で指定するサイズは、ページ単位で割り切れなければなりません。

また、memmap() を使ってユーザー空間にマッピングされた先頭アドレスはページ単位でアライメントされています。もともとのレジスタ空間の開始アドレスがページ単位でアライメントされていない場合には、その分補正が必要です。

UIO の仕様では、memmap() で指定するオフセットは、複数のレジスタ空間を持つ場合にどのレジスタ空間かを指定する index にページサイズを乗じた値を指定します。これにより、どのレジスタ空間をマッピングするかを特定します。

uio.py
    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 MemoryMappedIO(memmap, regs_offset, regs_length)

使用例

sample.py

from mmapio 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)))

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?