6
6

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 と Numpy で UIO を制御

Last updated at Posted at 2017-11-27

はじめに

UIO とは

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

ユーザー空間で UIO を利用する際は、/dev/uio0 を open して mmap すると、デバイスのレジスタ空間が見えます。この記事では、Python と numpy を使って UIO のデバイスレジスタをアクセスする方法を示します。

Uio クラス

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

Uio クラスでは numpy、mmap、os、glob、re パッケージを使うのでインポートします。

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

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 のデバイスレジスタをアクセスするための Uio.Regs クラスのインスタンスを生成します。Uio.Regs に関しては次節を参照してください。

インスタンスを生成する際、前節で説明した複数のレジスタ空間を持つ場合にどのレジスタ空間かを指定する 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 Uio.Regs(memmap, regs_offset, regs_length)

Uio.Regs クラス

mmap.mmap() でユーザー空間にマッピングしているレジスタ空間を numpy の frombuffer() を使って numpy の配列として見えるようにします。レジスタへのアクセスは、ここで作った numpy の配列にアクセスすることで行います。

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

使用例

sample.py

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

6
6
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
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?