kernel moduleの個人的なテンプレート

キャラクタデバイスドライバのkernel moduleの個人的なテンプレート
kernelは4あたり

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <asm/uaccess.h>

#define DEVNAME "hello"

#define MINOR_COUNT 1

MODULE_LICENSE("Dual BSD/GPL");

struct hello_dev
{
	int major;
	int minor;
	dev_t dev_id;
	struct cdev *pcdev;
	struct class* pclass;
	struct device* pdevice;
};

static struct hello_dev *pdev;

static int hello_open(struct inode *inode, struct file* flip)
{
	pr_info("dev open\n");
	return 0;
}

static int hello_release(struct inode *inode, struct file* flip)
{
	pr_info("dev close\n");
	return 0;
}

static ssize_t hello_read(struct file *flip, char __user *buf, size_t count, loff_t *offset)
{
	pr_info("dev read\n");
	return 0;
}

static ssize_t hello_write(struct file *flip, const char __user *buf, size_t count, loff_t *offset)
{
	pr_info("dev write\n");
	return 0;
}

static long hello_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
	pr_info("dev ioctl\n");
	return 0;
}

static struct file_operations hello_fops =
{
	.owner = THIS_MODULE,
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write,
	.unlocked_ioctl = hello_ioctl,
};

static int hello_init(void)
{
	int ret = -ENOMEM;

	pr_info("hello\n");

	pdev = kmalloc(sizeof(struct hello_dev), GFP_KERNEL);

	if(pdev == NULL)
	{
		pr_err("cannot alloc kern mem\n");
		return -ENOMEM;
	}

	pdev->pcdev = kmalloc(sizeof(struct cdev), GFP_KERNEL);

	if(pdev->pcdev == NULL)
	{
		pr_err("cannot alloc kern mem for cdev\n");
		goto pdev_free;
	}

	ret = alloc_chrdev_region(&(pdev->dev_id),
					0,
					MINOR_COUNT,
					DEVNAME);

	if(ret < 0)
	{
		pr_err("cannot alloc chrdev region\n");
		goto cdev_free;
	}

	cdev_init(pdev->pcdev, &hello_fops);

	pdev->pcdev->owner = THIS_MODULE;
	
	ret = cdev_add(pdev->pcdev, pdev->dev_id, MINOR_COUNT);

	if(ret < 0)
	{
		pr_err("fail to add cdev\n");
		goto cdev_unregist;
	}

	pr_info("mojor:%d\n", MAJOR(pdev->dev_id));
	pr_info("minor:%d\n", MINOR(pdev->dev_id));

	pdev->pclass = class_create(THIS_MODULE, DEVNAME);

	if(IS_ERR(pdev->pclass))
	{
		pr_err("fail to create class\n");
		ret = PTR_ERR(pdev->pclass);
		goto cdev_del;
	}

	pdev->pdevice = device_create(pdev->pclass, NULL,
						pdev->dev_id,
						NULL,
						DEVNAME);

	if(IS_ERR(pdev->pdevice))
	{
		pr_err("fail to create device\n");
		ret = PTR_ERR(pdev->pdevice);
		goto class_destroy;
	}

	pr_info("create success\n");
	return 0;

class_destroy:
	class_destroy(pdev->pclass);

cdev_del:
	cdev_del(pdev->pcdev);

cdev_unregist:
	unregister_chrdev_region(pdev->dev_id, MINOR_COUNT);

cdev_free:
	kfree(pdev->pcdev);

pdev_free:
	kfree(pdev);

	return ret;
}

static void hello_exit(void)
{
	device_destroy(pdev->pclass, pdev->dev_id);
	class_destroy(pdev->pclass);
	cdev_del(pdev->pcdev);
	unregister_chrdev_region(pdev->dev_id, MINOR_COUNT);
	kfree(pdev->pcdev);
	kfree(pdev);
	pr_info("Goodbye...\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_DESCRIPTION("kernel module template");

stackoverflow.com

書き方を調べていたところ
cdevを登録するのに2つの方法があるらしい。

int register_chrdev(unsigned int major, const char *name,
    struct file_operations *fops);

struct cdev my_cdev ;
...
cdev_init(&my_cdev,&my_fops);
cdev_add(&my_cdev,num, count)

という方法らしいが、前者はcdevを意識する必要がない。

stackoverflow.com
これは前者でいいじゃんみたいな感じ。。

しかしここで(ちょっと古い)
The cdev interface [LWN.net]
cdev_addが行われるとすぐにkernelによってファイルオペレーションを呼ぶことが可能なので、
呼関連付けられているデバイスの完全な初期化が終わるまではcdev_addを呼ぶべきではない。

と書かれている。

これを踏まえるとデバイスがちゃんと見つかって、probe関数が呼ばれたときに、デバイスを初期化して、
最後にcdev_addするのが良いのだろうか・・・

Bash on Windowsをイイカンジにする

右クリックでBash起動
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\bash\command]
@="C:\\Windows\\System32\\bash.exe"


少し前のアップデートによって、Bash On Windowsから
Windowsのアプリケーション(.exe)が起動できるようになった。
ので

Bashからexplorerでその場を開く。

.bashrcや.zshrcなどに

alias explorer="explorer.exe \".\""
適当なアプリで開く
alias hoge="cmd.exe /c \"start $1\""

Bashからデフォルトのアプリで開く。
Macのopenコマンドのようなもの。
(Linuxには別の目的のopenコマンドがすでにある。
何かよいエイリアス名にしたい。)

zybo 基板寸法

zyboを購入した。

のは良いが、裏面がむき出しなので、電源を入れたままスチールラックの上にうっかりおいた日にはショートして壊してしまう可能性がある。

裏面にアクリルの板を取り付ける。

 

Digilentのサイトを見ても寸法が書いていないので

適当なところから3Dモデルをダウンロードし、適当な3DCADで開いた。

f:id:katakanan:20170413224105p:plain(なんだかHDMIのモデルがおかしい)

これから作った図面

f:id:katakanan:20170413225638p:plain

単位はmm

 

寸法が微妙だなとおもったらコレinchだ

121.92 mm = 8.4 inches

83.82 mm = 3.3 inches

3.81 mm = 0.15 inches = 150 mil

80.01mm = 3.15 inch

118.11mm = 4.65 inch

あとは適当なホームセンターでレーザー加工を頼んで

終了

 

だが、Zyboにもとからついている足のネジ穴にM3のネジが入らない。

おそらくインチネジだからだと思われる(?)。

ヤード・ポンド法やめてメートル法に統一してほしい。

zybo 割り込み④ まとめ

e-tipsmemo.hatenablog.com
これらのまとめ
insmodの後に(デバイスが見つかったので)probeが呼ばれて幾つかのリソースの値を取得できている事がわかる。
もしprobeが呼ばれない場合は、compatibeが間違っていることやdevicetreeが間違っているなど。

zynq> insmod mysw.ko
probe
name:/amba_pl/mySw@43c10000
start:0x43c10000
size:0x00010000
irq_num:61
registered IRQ 61
zynq> interrupt occured 61
interrupt occured 61
zynq> cat /proc/interrupts
           CPU0       CPU1
 27:          0          0       GIC  27  gt
 29:       5389       5619       GIC  29  twd
 35:          0          0       GIC  35  f800c000.ocmc
 39:         43          0       GIC  39  f8007100.adc
 40:          0          0       GIC  40  f8007000.devcfg
 41:          0          0       GIC  41  f8005000.watchdog
 43:          0          0       GIC  43  ttc_clockevent
 45:          0          0       GIC  45  f8003000.dmac
 46:          0          0       GIC  46  f8003000.dmac
 47:          0          0       GIC  47  f8003000.dmac
 48:          0          0       GIC  48  f8003000.dmac
 49:          0          0       GIC  49  f8003000.dmac
 51:          0          0       GIC  51  e000d000.spi
 56:         79          0       GIC  56  mmc0
 57:          0          0       GIC  57  cdns-i2c
 61:          9          0       GIC  61  mySW_hoge
 72:          0          0       GIC  72  f8003000.dmac
 73:          0          0       GIC  73  f8003000.dmac

もっと実用的なデバイスドライバでは
仮想アドレスとのマッピングなど(?)を行うことなどが必要そうだ。
いろいろなサンプルや、やってみた記事がネットに溢れているがあまり一貫して最初から書いてあったものはなかったような気がしたので備忘録。

zybo 割り込み③ device driver編


e-tipsmemo.hatenablog.com


の続き。デバイスドライバは書いたことがなかったので備忘録として
参考資料たち。

Introduction to Linux Device Drivers - Part 1 The Basics

Introduction to Linux Device Drivers - Part 2 Platform and Character Drivers


どうやらデバイスドライバには種類があって

キャラクタ、ブロックなど。

動画ではキャラクタドライバについて解説していて、

/devや/class以下にデバイスに対応するディスクリプタを作成。

ユーザープログラムからは、それをopen, write, read...などしてデバイスと情報をやり取りする。

デバイスドライバで書くものは、そのopenやwrite, read...が呼ばれたときの動作。(という感じらしい。)

今回はまだそこまで作り込まないで
純粋にデバイスドライバの最初の処理と、割り込みハンドラだけを入れて動作を確認する。

platform device API

The platform device API [LWN.net]
これを利用する。

Makefile

ZYBO-Embedded_Linux_Hands-on_Tutorial.pdf
のp28
Makefileが乗っている。

P_DIR := $(shell pwd)
obj-m :=  mysw.o
 
all:
	make -C ../../Linux-Digilent-Dev/ M=$(P_DIR) modules
 
clean:
	make -C ../../Linux-Digilent-Dev/ M=$(P_DIR) clean

Man page of MAKE

`-C dir, --directory=dir
makefile を読み込むなどの動作の前に、ディレクトリ dir に移動する。複数の -C オプションが指定されている場合、それぞれは前の指定に対する相対パスと解 釈される。例えば、 -C / -C etc は -C /etc と同じ意味である。このオプションは通常、 make を再帰的に呼び出す時に使われる。

本体コード
#include <linux/init.h>
#include <linux/module.h>
#include <asm/uaccess.h>    /* Needed for copy_from_user */
#include <asm/io.h>         /* Needed for IO Read/Write Functions */
#include <linux/proc_fs.h>  /* Needed for Proc File System Functions */
#include <linux/seq_file.h> /* Needed for Sequence File Operations */
#include <linux/platform_device.h>  /* Needed for Platform Driver Functions */
#include <linux/slab.h> /* Needed for kmalloc and kfree */
#include <linux/interrupt.h>

#define DRIVER_NAME "mySw"
#define DEVICE_NAME "mySW_hoge"

struct resource *res;
int irq;
unsigned long remap_size;

static irqreturn_t mysw_isr(int irq, void *dev_id)
{
	printk("interrupt occured %d\n", irq);
	return IRQ_HANDLED;
}

static int simple_probe(struct platform_device *pdev)
{
	struct resource *irq_res;

	printk(KERN_ALERT "probe\n");
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if(!res)
	{
		dev_err(&pdev->dev, "cant get mem resource\n");
		return -ENODEV;
	}

	remap_size = res->end - res->start + 1;
	
	printk(KERN_INFO"name:%s\n", res->name);
	printk(KERN_INFO"start:0x%08lx\n",(unsigned long)res->start);
	printk(KERN_INFO"size:0x%08lx\n",(unsigned long)remap_size);

	//irq = platform_get_irq(pdev, 0);
	irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if(!irq_res)
	{
		dev_err(&pdev->dev, "cant get irq resource\n");
		return -ENODEV;
	}

	irq = irq_res->start;

	if(irq < 0)
	{
		dev_err(&pdev->dev, "cant get irq resource\n");
		return -ENODEV;
	}

	printk(KERN_INFO"irq_num:%d\n", irq);

	if(request_irq(irq, mysw_isr, IRQF_TRIGGER_RISING, DEVICE_NAME, NULL))
	{
		printk(KERN_ERR"fail to request irq %d\n", irq);
		return -EIO;
	}
	else
	{
		printk(KERN_INFO"registered IRQ %d\n", irq);
	}

	return 0;

}

static int simple_remove(struct platform_device *pdev)
{
	free_irq(irq, NULL);
	printk(KERN_ALERT "removed\n");
	return 0;
}

static const struct of_device_id simple_of_match[] = {
     {.compatible = "xlnx,mySw-1.0"},
     {},
};

MODULE_DEVICE_TABLE(of, simple_of_match);

static struct platform_driver simple_driver = {
	.driver = {
	.name = DRIVER_NAME,
	.owner = THIS_MODULE,
	.of_match_table = simple_of_match,
	},
	.probe = simple_probe,
	.remove = simple_remove,
};

module_platform_driver(simple_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("HOGE.");
MODULE_DESCRIPTION(DRIVER_NAME ": MYLED driver (Simple Version)");
MODULE_ALIAS(DRIVER_NAME);

最初に実行されるのはinsmodされたときに
module_platform_driver();
のマクロによって展開された__init ~~~ ()という関数

Linux/include/linux/platform_device.h - Linux Cross Reference - Free Electrons

227 #define module_platform_driver(__platform_driver) \
228 module_driver(__platform_driver, platform_driver_register, \
229 platform_driver_unregister)

Linux/include/linux/device.h - Linux Cross Reference - Free Electrons

1458 #define module_driver(__driver, __register, __unregister, ...) \
1459 static int __init __driver##_init(void) \
1460 { \
1461 return __register(&(__driver) , ##__VA_ARGS__); \
1462 } \
1463 module_init(__driver##_init); \
1464 static void __exit __driver##_exit(void) \
1465 { \
1466 __unregister(&(__driver) , ##__VA_ARGS__); \
1467 } \
1468

initでやらせたい処理もないので、
module_platform_driver(simple_driver);を使う。

(もし別々に書く必要があるなら
手動で module_init()マクロを使って
initする関数の中で個別にplatform_driver_registerやplatform_device_register_simpleなどを呼ぶ必要があり、
exitの前にplatform_driver_unregisterなどをする。
Linux Kernel: How do the probe function of Device Driver gets called? - Quora)
今回は深追いしない。

static const struct of_device_id simple_of_match[] = {
     {.compatible = "xlnx,mySw-1.0"},
     {},
};

MODULE_DEVICE_TABLE(of, simple_of_match);

が重要で.compatibleにはdevicetreeで自作したデバイスの定義内に入れたcompatibleプロパティと同じ。

insmod hoge.koをしたあとは(__init~~が呼ばれて色々あったあとに)デバイスが見つかったらprobeが呼ばれる。

なのでsimple_probe関数内で
バイスのリソースを取得し、
割り込み番号から割り込みハンドラを登録する。

次はまとめ

zybo 割り込み② SDK, devicetree編

前回の続き
e-tipsmemo.hatenablog.com

SDK

VivadoでBitstreamがでたら。
File->Export Hardwareでhdfファイルを出力する。(Include bitstreamにチェックをいれる。)
File->Launch SDK

SDKの準備

Xilinx Wiki - Build Device Tree Blob

google:xilinx sdk device tree generator
あたりを参考にし、device treeを自動生成するツールを入れていおく。

Device Treeとその編集

File->New->Board Support Package
f:id:katakanan:20170409013859p:plain
にて、device_treeを選択してFinish

Overview->device_treeのbootargsに
console=ttyPS0,115200 root=/dev/ram rw earlyprintk rootwait devtmpfs.mount=1 earlycon earlycon earlycon
を入力。
(デフォルトのドライバの設定などもできるらしい?)
「OK」で必要なDeviceTreeが分割された状態で生成される。

Interruptの関係のプロパティは自動で入れてくれないので、自分て追記する。
zynq-7000.dtsiをみると、割り込みのコントローラーである
cortex-a9-gicが、intc: interrupt-controller@f8f01000となっている。
pl.dtsiの自分の追加した割り込み信号を発生するロジックのモジュール内に以下の文言を入れる。

interrupt-parent = <&intc>;
interrupts = <0 29 4>;

f:id:katakanan:20170409015147p:plain

devicetreeについては
Device Tree Usage - eLinux.org
など
interruptのプロパティの数字については
Solved: IRQF2P Interupt not being seen in driver when movi... - Community Forums
より、

For interrupts = < 0 29 4 >, 0 is defining it as a SPI ( general purpose ) interrupt, 29 is the LSB of IRQ_F2P ( TRM says 61, subtract 32 ), and 4 is level-high triggered.

(割り込み番号は29から順番?
次に割り込みのデバイスを入れたら30になると..)

このdtsをコンパイルし、dtbを作成する。

dtc -I dts -O dtb -o devicetree.dtb system-top.dts
FSBL, uImage, uramdisk.image.gz

digilentGithubリポジトリをcloneして
Linuxをビルド。
uImageを作成。
u-boot.elfを同様にして作成する。

一方SDKでは
File->New->Application Project
nameをfsblにしてnext。
Zynq FSBLを選択してFinish
FSBLがビルドされてfsbl.elfができていたら
プロジェクトを右クリックしてCreate Boot Image
Addで最後のパーティションにu-boot.elfを追加する。
Create Boot ImageでBoot.binが作られる。

uramdisk.image.gzはzedboardかどこかの別のプロジェクトに入っていたものを持ってきた。

(ramdiskを編集したいときはイメージをマウントする必要があるので
Bash On windowsだけではできなかった。。。)

  • boot.bin
  • devicetree.dtb
  • uramdisk.image.gz
  • uImage

これらが最低限必要なもの。

次はデバイスドライバ

zybo 割り込み① PS,PL設定編

zyboというFPGAの入ったARMコアのSocが乗っかっている評価基板を買った。
CPUとFPGAを組み合わせた動作のために、
まずは、FPGAからARM側への割り込みを試してみる。
その過程で知ったこと、使ったサイトのメモ。

全体を通して参考になるページ
http://yuki-sato.com/wordpress/fpga/?q=%2Fcategory%2F%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C%2Ffpga%2F

必要なもの

Vivado
Zybo

PSの設定

VivadoでFPGA側の回路をつくる。
いろいろなチュートリアルを見ると、基本的にはBlock Design上でHDLのブロックなりIPなりを追加、接続していく。
IP Catelogからzynqを追加して必要設定を行う。
PS-PL Configuration

  • S AXI GP0 Interface

Peripheral I/O Phins

 (SD0でCard Detectをチェックして47番pinに設定しないとLinuxがブートしない。)

  • UART1
  • I2C 0


Interrupts
IRQ_F2P

Clock Configurationで周辺の回路に沿うように設定する。
ZyboだとPSの入力周波数は50MHzになっている。
Zybo Reference Manual [Reference.Digilentinc]
12 Clock Source

Processor/Memory/Clocksで、CPUの周波数を650MHzとDDRを525MHzにする。
IO Peripheral ClocksでSDIOを50MHzにする。
PL Fabric ClocksFLCK_CLK0を有効にして100MHzを出力するようにする。

DDR ConfigurationでMemory TypeはMT41K128M16 JT-125にする。
(実装されているDDR3の名前は違っているがコレでいいらしい)

(これは個人的な備忘録なのでバージョンが変わったりなどしたらDigilentのプロジェクトを真似したほうが確実かもしれない)

PLの構成

画像内のようにスイッチやLEDを接続するAXI LiteのIPコアを作った。
f:id:katakanan:20170409010345p:plain
LEDの一つはスイッチの割り込み出力へつなげた。

xlconcatで幾つかの割り込みを束ねてIRQ_F2Pへつなげる。
(今回は一つ)
これで適切にスイッチとLEDのポートを設定した後、bitstreamを出す。

次はSDK