Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
What to Upload to SlideShare
Loading in …3
×
1 of 44

ラズパイでデバイスドライバを作ってみた。

3

Share

Download to read offline

チップセットの概要とデバイスドライバの実装の話。

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

ラズパイでデバイスドライバを作ってみた。

  1. 1. RaspberryPiで デバイスドライバを作ってみた。 byk‑onishi
  2. 2. 自己紹介 名前:大西和貴( @_k_onishi_ ) 出身:京都 所属:SAKURAInternetInc. アプリケーションエンジニア (Go(Goji),Typescript(React.js),PHP,LXC,Etc...) 趣味: CPU,Kernel,仮想化,CTF(Pwn),マルウェア 読書、ピアノ、ギター、カラオケ
  3. 3. 概要 対象となるチップセットのメモリマップの概要 及びキャラクタデバイスドライバの実装。
  4. 4. 環境 PaspberryPi3ModelB $ sudo cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)" NAME="Raspbian GNU/Linux" VERSION_ID="9" VERSION="9 (stretch)" ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs" $ sudo uname -a Linux raspberrypi 4.9.41-v7+ #1023 SMP Tue Aug 8 16:00:15 BST 2
  5. 5. Chipset まずデータシートからチップセットを調べる。 Processer BroadcomBCM2387chipset. 1.2GHzQuad‑CoreARMCortex‑A53(64Bit) 引用:https://www.terraelectronica.ru/pdf/show? pdf_file=%2Fds%2Fpdf%2FT%2FTechicRP3.pdf 上記から BCM2387 であることがわかる。
  6. 6. BCM2387 https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdf
  7. 7. ペリフェラルレジスタ ペリフェラルデバイスのレジスタは、ペリフェラル(プログラマブ ル・カウンタや割り込み制御、シリアル通信ポートなどのハードウ ェア)の動作を設定したり、動作状況を読み出したりするためのレ ジスタである。 引用:https://ja.wikipedia.org/wiki/レジスタ_(コンピュータ)#ペリ フェラルデバイスのレジスタ
  8. 8. データシートを見ると以下のような記述があった。 Peripherals(atphysicaladdress0x3F000000on)aremapped intothekernelvirtualaddress spacestartingataddress0xF2000000. 引用:https://web.stanford.edu/class/cs140e/docs/BCM2837‑ ARM‑Peripherals.pdfp6 上記からペリフェラルレジスタが 0x3F000000 からマッピングされてい ることがわかる。
  9. 9. ドキュメントから bcm_host_get_peripheral_address() でペリフェ ラルレジスタのアドレスを取得できることがわかったので実際に以下の コードで確かめる。 https://www.raspberrypi.org/documentation/hardware/raspberrypi/peri pheral_addresses.md $ cat samples/get_peripheral_address.c #include <stdio.h> #include <bcm_host.h> int main(int argc, char* argv[]) { printf("0x%08Xn", bcm_host_get_peripheral_address()); return 0; } $ gcc samples/get_peripheral_address.c -I/opt/vc/include -L/opt $ ./myfile 0x3F000000
  10. 10. /proc/iomem からもメモリマップが確認できる。 $ sudo cat /proc/iomem 00000000-3b3fffff : System RAM 00008000-00afffff : Kernel code 00c00000-00d3da63 : Kernel data 3f006000-3f006fff : dwc_otg 3f007000-3f007eff : /soc/dma@7e007000 3f00b840-3f00b84e : /soc/vchiq 3f00b880-3f00b8bf : /soc/mailbox@7e00b880 3f101000-3f102fff : /soc/cprman@7e101000 3f200000-3f2000b3 : /soc/gpio@7e200000 3f201000-3f201fff : /soc/serial@7e201000 3f201000-3f201fff : /soc/serial@7e201000 3f202000-3f2020ff : /soc/sdhost@7e202000 3f215000-3f215007 : /soc/aux@0x7e215000 3f300000-3f3000ff : /soc/mmc@7e300000 3f980000-3f98ffff : dwc_otg
  11. 11. GPIO制御用レジスタ TheGPIOhas41registers.Allaccessesareassumedtobe32‑bit. https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdfp90 BCM2387のデータシートp90にGPIOレジスタのマッピング表が記載さ れている。
  12. 12. Address Field Name Description Size Read/Write 0x7E200000 GPFSEL0 GPIOFunction Select0 32 R/W 0x7E200004 GPFSEL1 GPIOFunction Select1 32 R/W 0x7E200008 GPFSEL2 GPIOFunction Select2 32 R/W 0x7E20000C GPFSEL3 GPIOFunction Select3 32 R/W 0x7E200010 GPFSEL4 GPIOFunction Select4 32 R/W 0x7E200014 GPFSEL5 GPIOFunction Select5 32 R/W
  13. 13. Address Field Name Description Size Read/Write 0x7E20001C GPSET0 GPIOPinOutput Set0 32 W 0x7E200020 GPSET1 GPIOPinOutput Set1 32 W 0x7E200024 ‑ Reserved ‑ ‑ 0x7E200028 GPCLR0 GPIOPinOutput Clear0 32 W 0x7E20002C GPCLR1 GPIOPinOutput Clear1 32 W 0x7E200030 ‑ Reserved ‑ ‑ 0x7E200034 GPLEV0 GPIOPinLevel0 32 R 0x7E200038 GPLEV1 GPIOPinLevel1 32 R
  14. 14. GPIO"FunctionSelect"Registers(GPFSEL0~ GPFSEL5) Thefunctionselectregistersareusedtodefinetheoperationof thegeneral‑purposeI/O pins https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdfp91
  15. 15. Bit(s) FieldName Description Type Reset 29‑27 FSEL9 FSEL9‑FunctionSelect9 000=GPIOPin9isaninput 001=GPIOPin9isanoutput R/W 0 26‑24 FSEL8 FSEL8‑FunctionSelect8 R/W 0 23‑21 FSEL7 FSEL7‑FunctionSelect7 R/W 0 20‑18 FSEL6 FSEL6‑FunctionSelect6 R/W 0 17‑15 FSEL5 FSEL5‑FunctionSelect5 R/W 0
  16. 16. Bit(s) FieldName Description Type Reset 14‑12 FSEL4 FSEL4‑FunctionSelect4 R/W 0 11‑9 FSEL3 FSEL3‑FunctionSelect3 R/W 0 8‑6 FSEL2 FSEL2‑FunctionSelect2 R/W 0 5‑3 FSEL1 FSEL1‑FunctionSelect1 R/W 0 2‑0 FSEL0 FSEL0‑FunctionSelect0 R/W 0 3bitずつが各ピンに対応しており、同じ要領で GPFSEL5 まで続き54ピ ン全てに対応する。
  17. 17. GPIOPinOutputSetRegisters(GPSET0~GPSET1) TheoutputsetregistersareusedtosetaGPIOpin. https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdfp95 アウトプット用のレジスタで各ビットが各ピンに対応している。 GPSET0 を例に対応表をいかに示す。 Bit(s) FieldName Description Type Reset 31‑0 SETn(n=0..31) 0=Noeffect 1=SetGPIOpinn R/W 0 同じ要領で GPSET1 を用いて54ピンまで対応している。
  18. 18. GPIOPinOutputClearRegisters(GPCLR0~ GPCLR1) Theoutputclearregisters)areusedtoclearaGPIOpin. https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdfp95 これはアウトプットのクリア用レジスタで先ほどと同様に各ビットが各 ピンに対応している。 GPCLR0 を例に対応表をいかに示す。 Bit(s) FieldName Description Type Reset 31‑0 CLRn(n=0..31) 0=Noeffect 1=ClearGPIOpinn R/W 0 同じ要領で GPCLR1 を用いて54ピンまで対応している。
  19. 19. GPIOPinLevelRegisters(GPLEV0~GPLEV1) Thepinlevelregistersreturntheactualvalueofthepin. https://web.stanford.edu/class/cs140e/docs/BCM2837‑ARM‑ Peripherals.pdfp96 ピンの値を取得する用のレジスタで先ほどと同様に各ビットが各ピンに 対応している。 GPLEV0 を例に対応表をいかに示す。 Bit(s) FieldName Description Type Reset 31‑0 LEVn (n=0..31) 0=GPIOpinnis low 1=GPIOpinnis high R/W 同じ要領で GPLEV1 を用いて54ピンまで対応している。
  20. 20. 実装に関して /dev/mem mmap() PAGE_SIZE
  21. 21. /dev/mem memはコンピュータのメインメモリーイメージのキャラクターデ バイスファイル(characterdevicefile)である。 https://linuxjm.osdn.jp/html/LDP_man‑pages/man4/mem.4.html
  22. 22. mmap() ファイルやデバイスをメモリーにマップ/アンマップする。 https://linuxjm.osdn.jp/html/LDP_man‑pages/man2/mmap.2.html 当該関数を用いて先ほどのデバイスファイルをプロセスのメモリにマッ ピングしアクセスを行う。
  23. 23. PAGE_SIZE 仮想メモリを扱う単位であるページのサイズを取得する。これは mmap() 関数でマッピングするメモリのサイズに用いる(ページサイズ でマッピングするのが定石らしい) $ getconf PAGE_SIZE 4096
  24. 24. 実装 https://github.com/k‑onishi/gpio‑lib
  25. 25. キャラクタデバイス作成(カーネルモジュール) システムコールに対応する関数の作成 メジャー番号とマイナー番号の取得 キャラクタデバイス構造体の初期化 ドライバの登録 デバイスのクラス登録 デバイスファイルの作成
  26. 26. システムコール対応の関数テーブル作成 struct file_operations my_file_ops = { .owner = THIS_MODULE, .open = my_open, .release = my_close, .read = my_read, .write = my_write, .unlocked_ioctl = my_ioctl, /* 64 bits */ .compat_ioctl = my_ioctl, /* 32 bits */ }; open 及び release で物理アドレスと仮想アドレスのマッピングを行 い、 ioctl で操作対象のピンの変更、そして実際の読み書きは read 及 び write で行う。
  27. 27. file_operations は実装できるシステムコールに対応した処理を保持 する構造体で以下のように定義されている。 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t ssize_t (*aio_read) (struct kiocb *, char __user *, ssize_t (*write) (struct file *, const char __user *, ssize_t (*aio_write) (struct kiocb *, const char __user int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_ int (*ioctl) (struct inode *, struct file *, unsigned long (*unlocked_ioctl) (struct file *, unsigned int long (*compat_ioctl) (struct file *, unsigned int, int (*mmap) (struct file *, struct vm_area_struct *);
  28. 28. int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datas int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, ssize_t (*writev) (struct file *, const struct iovec *, ssize_t (*sendfile) (struct file *, loff_t *, size_t ssize_t (*sendpage) (struct file *, struct page *, unsigned long (*get_unmapped_area)(struct file *, int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long int (*flock) (struct file *, int, struct file_lock *); };
  29. 29. メジャー番号とマイナー番号の取得 以下では alloc_chrdev_region 関数でメジャー番号の取得を行い、 dev_t 型の変数から MAJOR マクロを用いてメジャー番号を取得してい る。 dev_t dev; alloc_ret = alloc_chrdev_region(&dev, MINOR_NUMBER_START, NUMBE if (alloc_ret != 0) { printk(KERN_ERR "failed to alloc_chrdev_region()n"); return -1; } major_number = MAJOR(dev);
  30. 30. dev_t は以下のように定義されており unsigned int であることがわ かる。そしてその変数から決められた上位12ビットをメジャー番号とし ている。 typedef __u32 __kernel_dev_t; typedef __kernel_dev_t dev_t; // include/linux/kdev_t.h #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
  31. 31. alloc_chrdev_region() は以下のように定義されており __register_chrdev_region の戻り値からメジャー番号とマイナー番 号を設定している。 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigne const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
  32. 32. キャラクタデバイス構造体の初期化 /* initialize cdev and function table */ cdev_init(&my_char_dev, &my_file_ops); my_char_dev.owner = THIS_MODULE;
  33. 33. cdev_init() は以下のように定義されておりキャラクタデバイス構造 体のopsメンバに引数であるシステムコールハンドラ関数のテーブルを 設定している。 void cdev_init(struct cdev *cdev, struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); cdev->kobj.ktype = &ktype_cdev_default; kobject_init(&cdev->kobj); cdev->ops = fops; }
  34. 34. cdev は以下のように定義されており、先ほど作成したシステムコール テーブルを保持するメンバが存在する。 struct cdev { struct kobject kobj; struct module *owner; struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
  35. 35. ドライバの登録 デバイスドライバの登録は cdev_add() で行う。キャラクタデバイス構 造体やデバイス番号を保持している変数、当該デバイスに割り当てるマ イナー番号の数などの設定を行う。 cdev_err = cdev_add(&my_char_dev, dev, NUMBER_MINOR_NUMBER); if (cdev_err != 0) { printk(KERN_ERR "failed to cdev_add()n"); unregister_chrdev_region(dev, NUMBER_MINOR_NUMBER); return -1; }
  36. 36. デバイスのクラス登録 class_create() でクラス登録を行う。当該処理 で /sys/class/my_device/ が作成される。 my_char_dev_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(my_char_dev_class)) { printk(KERN_ERR "class_create()n"); cdev_del(&my_char_dev); unregister_chrdev_region(dev, NUMBER_MINOR_NUMBER); return -1; }
  37. 37. デバイスファイルの作成 device_create() で実際に指定のメジャー番号及びマイナー番号のデ バイスを登録する。(ex. sys/class/my_device/my_device ) device_create(my_char_dev_class, NULL, MKDEV(major_number,
  38. 38. 実装に関して module_init()&module_exit() PAGE_SIZE ioremap_nocache() get_user()&put_user();
  39. 39. module_init()&module_exit() モジュールのロード及びアンロード時に呼び出される関数を登録。 先ほどの説明した一連の処理は module_init の引数となっている関数 内で行う。 module_init(my_init); module_exit(my_exit);
  40. 40. PAGE_SIZE カーネルが仮想アドレス空間においてメモリをチャンクとして扱う際の サイズ。 // include/asm-i386/page.h #define PAGE_SHIFT 12 #define PAGE_SIZE (1UL << PAGE_SHIFT)
  41. 41. ioremap_nocache() カーネル空間に物理アドレスのマッピングを行う。 base_address = (int)ioremap_nocache( GPIO_ADDRESS, PAGE_SIZE); return 0;
  42. 42. get_user()&put_user() ユーザ空間<‑>カーネル空間でデータのコピーを行う。 static ssize_t my_write(struct file* file, const char __user* b { int mode; get_user(mode, &buff[0]); : (省略) :
  43. 43. static ssize_t my_read(struct file* file, char __user* buff, size_t count, loff_t *pos) { int num_reg = (pin_number / NUM_PIN_EACH_REG); int offset = (pin_number % NUM_PIN_EACH_REG); int addr = base_address + GPLEV0_OFFSET + (REG_GAP * num_reg); unsigned int reg_value = MEMORY(addr); int value = ((reg_value >> offset) & 1UL); put_user(value + '0', &buff[0]); return count; }
  44. 44. 実装 https://github.com/k‑onishi/gpio‑driver

×