投稿者「kunih」のアーカイブ

[Linux] 割込みとaffinity

Linux で割込みはどの CPU に入るのか? 決まった CPU に入れられるのか? という質問がよくあって、聞かれるごとに忘れてることが多いのでメモしておきます。

$ cat /proc/irq/<IRQ>/smp_affinity
f
$ echo 1 > /proc/irq/<IRQ>/smp_affinity

いきなり答えを書いてますが、要は procfs の割込み番号に対応した smp_affinity が CPUマスクを表していて、CPU0,1,2,3 を 4bit で表した 16進数 “f” が表示されています。つまり、CPU0,1,2,3 のどれかにこの割込みが入る、ということになります。

また、 smp_affinity に CPU マスクを設定すれば、指定した CPU にだけその割込みが入るようになります。上の例では “1” なので CPU0 だけに入ります。

arm64 (GICv3) での実装

では実際のところどうなってるのか気になるので、ソースを追ってみます。割込み関連は kernel/irq/manage.c が制御の起点になっていて、procfs からの要求は irq_select_affinity_usr() を経由して irq_setup_affinity() が呼ばれます。

int irq_setup_affinity(struct irq_desc *desc)
{
...
        cpumask_and(&mask, cpu_online_mask, set);
        if (cpumask_empty(&mask))
        cpumask_copy(&mask, cpu_online_mask); 
...
        ret = irq_do_set_affinity(&desc->irq_data, &mask, false); 

set に /proc/irq/<IRQ>/smp_affinity の値、cpu_online_mask に現在有効な CPU を表すマスク値が入り、and を取って 割込みを入れる CPU の候補を作ります。

int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask, bool force) 
{
...
        ret = chip->irq_set_affinity(data, mask, force); 

irq_do_set_affinity() では割込みが属している irqchip の関数を呼び出します。GICv3 は drivers/irqchip/irq-gic-v3.c の gic_set_affinity() です。

static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
                            bool force)
{
...
        if (force)
                cpu = cpumask_first(mask_val);
        else
                cpu = cpumask_any_and(mask_val, cpu_online_mask);
...
        enabled = gic_peek_irq(d, GICD_ISENABLER);
        if (enabled)
               gic_mask_irq(d);

        reg = gic_dist_base(d) + GICD_IROUTER + (gic_irq(d) * 8);
        val = gic_mpidr_to_affinity(cpu_logical_map(cpu));

        gic_write_irouter(val, reg);

まず cpumask_any_and() で動作している CPU と CPUマスク値の and を(再び)取り、割り込む CPU を決定します。any ではありますが、結局はマスクの若い番号が選ばれます。

GIC に “GICD_IROUTERn” というレジスタがあります。該当する割込みのレジスタに CPU を指定すれば完了です。とはいえ CPU の表現は単なる数字ではなく、”MPIDR” レジスタで表現された値を使います。

static u64 gic_mpidr_to_affinity(unsigned long mpidr)
{
        u64 aff;

        aff = ((u64)MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 |
               MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
               MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8  |
               MPIDR_AFFINITY_LEVEL(mpidr, 0));

        return aff;
} 

CPU は単純に 0,1,2,3… で表現しますが、実際にはいくつかの CPU がクラスタ(グループ)を形成し、いくつかのクラスタがさらにクラスタを形成し…というように階層化されています。1つのクラスタに入る CPU は個数やアーキテクチャが決まっていることから、例えば A73 x2 + A53 x4 のシステムだと2クラスタを形成します。MPIDR は 4階層で表現するため、A73 がクラスタ0を形成しているとして CPU0 が(0.0.0.0), CPU1 が (0.0.0.1)、A53 がクラスタ1を形成しているとして CPU2 が (0.0.1.0), CPU3 が (0.0.1.1), CPU4 が (0.0.1.2), CPU5 が (0.0.1.3) と表現します。

GICD_IROUTERn に設定する CPU値もこの4階層で表現しますが、MPIDR とはビット配置が異なるため gic_mpidr_to_affinity() で変換しています。

gic_write_irouter() で実際に書き込めば、割込みの CPU 指定は完了です。

[U-boot] SPIドライバが認識されない謎

U-boot で SPI ドライバを作成してたときのこと。
ドライバモデルに準拠して作成したにも関わらずドライバの関数が一切呼ばれず、どうしようもなくてソース解読を進めた結果、想定外の結末だったという話です。

SPI ドライバの作成例

最近の U-boot のデバイス対応は DM(DeviceModel) に準拠して作るようになっています。ドライバの作成はだいたい以下のような流れです。

  • DMに準拠した他の登録済ドライバを見てスケルトンを作る
  • 名前を決めて関数名などを合わせる
  • 関数の中身を実装する
  • 使った API に合わせた #include を追加する
  • DeviceTree のノードを作成する
  • Makefile と Kconfig を定義する
  • config 定義とビルド
  • 実機に投入して起動

スケルトンは U_BOOT_DRIVER() で定義した情報で probe(開始)、remove(終了)、ops(各機能)の各関数を定義します。また of_match で DeviceTree の定義も行います。SPI の場合はだいたい以下のようになります。

static const struct dm_spi_ops HOGE_spi_ops = {
	.claim_bus	= HOGE_spi_claim_bus,
	.release_bus	= HOGE_spi_release_bus,
	.xfer		= HOGE_spi_xfer,
	.set_speed	= HOGE_spi_set_speed,
	.set_mode	= HOGE_spi_set_mode,
};

static const struct udevice_id HOGE_spi_ids[] = {
	{ .compatible = "FOOBAR,HOGE-spi" },
	{ }
};

U_BOOT_DRIVER(HOGE_spi) = {
	.name	= "HOGE-spi",
	.id	= UCLASS_SPI,
	.of_match = HOGE_spi_ids,
	.ops	= &HOGE_spi_ops,
	.ofdata_to_platdata = HOGE_spi_ofdata_to_platdata,
	.probe	= HOGE_spi_probe,
	.remove	= HOGE_spi_remove,
};

実際の SPI ドライバの作り方は以下に結構いい説明がありました。他のドライバでも作り方の流れは参考になります。
https://github.com/qemu/u-boot/blob/master/doc/driver-model/spi-howto.txt

drivers/spi/Makefile と drivers/spi/Kconfig にそれぞれ情報を追加してドライバの実装は完了ですが、この DM では DeviceTree を読むので、”FOOBAR,HOGE-spi”を持つノードを DeviceTree に追加します。例えば以下のような感じ(だいぶ適当)。

&soc {
	spi0: spi@12000000 {
		compatible = "FOOBAR,HOGE-spi";
		status = "okay";
		reg = <0x12000000 0x100>;
		pinctrl = <&pinctrl_spi>;
	};
};

“CONFIG_DM_SPI” と Kconfig に作成したこのドライバの定義(例えば “CONFIG_HOGE_SPI”)を menuconfig などで定義しビルドします。ついでにコマンドから確認できるように CONFIG_CMD_SPI も定義します。

そして起動ROMに投入して U-boot 起動。”sspi” が SPI の送信コマンドで、ドライバの probe もこのコマンドがきっかけで走ります。

=> sspi 0:0 8 A
Invalid bus 0 (err=-19)

ん?ドライバ起動時に走る probe 関数も ofdata_to_platdata 関数も走っていないようです。compatible は合ってるし、”dm tree” でドライバ自体が定義されていることは確認できています。しかし起動しない。

訳が分からないので解読

メッセージ “Invalid bus” から追ってみます。簡易表記で関数呼び出し関係を示します。

spi_get_bus_and_cs()
	uclass_get_device_by_seq()
		uclass_find_device_by_seq() ⇒ -ENODEV
	"Invalid bus %d (err=%d)\n"

uclass_find_device_by_seq() は SPI デバイスを変数 dev で巡回し、dev->req_seq が与えられたシーケンシャル番号(0) と一致すれば OK、見つからなければ -ENODEV を返します。つまり、登録された SPI デバイスに番号がない(実際 dev->req_seq == -1)。

dev->req_seq はデバイス定義の初期段階 device_bind_common() で付与されるようです。

device_bind_common()
	dev_read_alias_seq(dev, &dev->req_seq);

alias ?? もしかして… DeviceTree には SPI のエイリアスは無し。spi0 を追加してみると…

	aliases {
		serial0 = &serial0;
		serial1 = &serial1;
		spi0 = &spi0;
	};
=> sspi 0:0 8 A
FF

値はともかくエラーがなくなりました!どうも 「シーケンシャル番号を持つデバイスは alias 定義があるものだけが有効になる」ということのようです。Linux ではこの定義はなくても有効にできるのですが、U-boot の場合は SPI だけでなく serial や I2C もこの罠にハマることになりそうです。

[Linux] kprobes

kernel や module をデバッグしたいと思ったときにいろんな方法がありますが、真っ先に思いつくのが printk() でメッセージや観測値を表示することだと思います。しかしいちいちビルドし直さなければならないこと、ソースを改変する必要があること、などあまりいい方法ではありません。

Linux にはデバッグのための手段がたくさん用意されています。kernel 内蔵の kdb/kgdb、kernel 動作の追跡を行う ftrace、プロセス動作に手を入れる ptrace、syscall やシグナルを監視する strace、実行状況や実行回数を監視する perf など。これらはプログラム動作を外から監視するものですが、動作そのものに割り込む機能として kprobes があります。

kprobes は特定の場所に breakpoint 命令を設定し、実行時に命令をフックして予め用意した関数を実行することができます。特定の場所の設定と関数は module として用意することで、kernel が起動した後でも module をロードして設定することができます。

kprobes は kernel config の以下を有効にします。使い方を見るために下の2行にてサンプルも有効にしておきます。

CONFIG_KPROBES=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_KPROBES=m

サンプルは samples/kprobes/kprobe_sample.c にあります。make modules でビルドすると、samples/kprobes/kprobe_sample.ko ができあがるので、以下のように割り込みたい関数ラベルを指定してロードします。

# insmod kprobe_sample.ko symbol=_do_fork
Planted kprobe at 0xffff000080102050

このサンプルでは、struct kprobe の .symbol に関数ラベルの文字列、.pre_handler に割り込む箇所を実行する前に実行したい関数、.post_handler に割り込む箇所を実行した後に実行したい関数をそれぞれ設定しています。この関数に表示したい情報や実行したい操作を呼び出すことで任意の場所に割り込んで操作することができます。割り込む箇所が関数ラベルから離れている場合は .offset に関数ラベルからのオフセットを記載します。

しかしながら、kprobes では任意の箇所に割り込むため、この割り込ませる関数はレジスタレベルで情報を取得することになります。引数の struct pt_regs はアーキテクチャ依存のレジスタセットを表しています。

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
	pr_info("<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
		p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
	...
	/* A dump_stack() here will give a stack backtrace */
	return 0;
}

struct kprobes を設定したら、register_kprobe() で登録、unregister_kprobe() で登録を解除するだけです。module の probe 関数に必要なだけ登録すれば、複数箇所に割り込むことができます。

レジスタレベルで情報を扱うため、c言語レベルのデバッグにはやや不向きなところもありますが、ビルドが不要でどこにでも割り込めるため強力なデバッグツールと言えます。

CONTINUE 展示 in 城陽

城陽市歴史民俗資料館にて、特別展示として表題のイベントが9/2まで実施されていました。
内容としては主に1970~2000年代のゲーム機の展示ですが、いろんな方面からさまざまな所蔵品が集められていて、これだけの数のゲーム機が揃うのはなかなか見られない機会でした。

ただ、コンパクトな展示場に凝縮されて並べている展示方法だったため、一つ一つの機材について解説を見たり思い返したりするというよりも、これだけの数の圧巻さを提示している感じでちょっともったいなかった気もします。

続きを読む

ALL ABOUT マイコン BASIC マガジン II SP in 大阪

今年初めに「ALL ABOUT マイコンBASICマガジンII」が有楽町のよみうりホールで1000人規模で開催され参加したのですが,その半年後にまさかの大阪でのスペシャル版として南港ATCホールにて開催されました.これまでの内容を踏襲しながらも,300人規模とあってより近い感じ,より踏み込んだ形でトークを聞くことができました.

続きを読む

[RPi][Linux] Raspberry Pi で SPI 通信

SPI通信が使える環境がないか探していて、Raspberry Pi なら 40pin ヘッダに出ていて直接使えるのでこれで何かモジュールを繋げば通信できそうということで試してみます。
Raspbian 環境なら手順がいろいろ書かれているので、ここでは敢えて mainline kernel 環境で SPI を有効にしてみることにします。SPI はシリアル通信の一つでI2Cと同様に周辺デバイスのアクセス手段として利用されています。

SPI 通信の動作を確認するため、対向デバイスとして温度センサモジュールを接続して温度を取得してみることにします。温度センサは ADT7310 を使いますが、モジュール化された “MDK001” が便利です。

続きを読む

GPD Pocket

Cloud Funding の “Indiegogo” で募集され 3,182,160 USD も集めた “GPD Pocket” ですが、やはり気になる機種ということで出資してたところ、無事出荷され入手できました。

Ubuntu 版は同価格ながら Windows ライセンスが付属しないのであまり選択する旨味はないのですが、BIOS update と OS の自力入れ替えが必要でハマると文鎮ループのリスクも考えられるので、敢えてこちらを選択したのでした。

続きを読む

[RPi] Pi Zero W

Raspberry Pi 3 を入手して間もないですが、海外通販にて Pi Zero W を入手しました。PIROMONI にて注文掛けていたのですが6日で無事到着しました。Anniversary 期間に間に合ったので £10 で入手でき(通常は £12)、せっかくなので Pibow ケースや Speaker pHAT も追加しました。送料は £5.5 です。

SoC は RPi 無印と同じ BCM2835 で ARM1176 コア、RAMは 512MB の Elpida チップが SoC 上にボンディングされています。外部端子は micro USB(OTG)、mini HDMI、micro SD カード、GPIO ピン、と RPi の互換を保ちながら最小構成でできています。Pi Zero W ではさらに Wi-Fi と Bluetooth が内蔵されています。

続きを読む