デバイスドライバは、今回作ったPL部を読み書きするためのものです。Linuxではダイレクトにメモリアクセスするのも出来ますが、チュートリアルに従いデバイスドライバを書きます。
Makefile
まず作業するディレクトリですが、digilentディレクトリに作ります
1 2 |
mkdir drivers cd drivers |
あとはここにMakefileという名前のファイルを作って
1 2 3 4 5 6 7 8 |
BUILD_DIR := $(shell pwd) obj-m := myled.o all: make -C ../Linux-Digilent-Dev/ M=$(BUILD_DIR) modules clean: make -C ../Linux-Digilent-Dev/ M=$(BUILD_DIR) clean |
を書き込みます。
注意点
- makeの左側はスペースでなくタブです。必ずタブでないといけないのでコピペする時は注意して下さい
- チュートリアルではM=$(PWD)となっていますが、$(PWD)だと空欄になってしまったので上記のように修正しました。
ちなみにLinux-Digilent-Devは前にコンパイルしたLinuxカーネルです。-Cはディレクトリの変更でMは作業ディレクトリを指します。つまりカーネルを使ってMにあるソースをコンパイルするわけです。
これでデバイスドライバを作成する準備が出来ました。このMakefileはmyled.cからmyled.oを作りmyled.koを作るためのMakefileです。これからmyled.c(つまりデバイスドライバ)を書きます。
デバイスドライバ
デバイスドライバはhttp://marsee101.blog19.fc2.com/blog-entry-2923.htmlを参考にインクルード文を1つ追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
#include <linux/kernel.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 */ /* Define Driver Name */ #define DRIVER_NAME "myled" unsigned long *base_addr; /* Vitual Base Address */ struct resource *res; /* Device Resource Structure */ unsigned long remap_size; /* Device Memory Size */ /* Write operation for /proc/myled * ----------------------------------- * When user cat a string to /proc/myled file, the string will be stored in * const char __user *buf. This function will copy the string from user * space into kernel space, and change it to an unsigned long value. * It will then write the value to the register of myled controller, * and turn on the corresponding LEDs eventually. */ static ssize_t proc_myled_write(struct file *file, const char __user * buf, size_t count, loff_t * ppos) { char myled_phrase[16]; u32 myled_value; if (count < 11) { if (copy_from_user(myled_phrase, buf, count)) return -EFAULT; myled_phrase[count] = '\0'; } myled_value = simple_strtoul(myled_phrase, NULL, 0); wmb(); iowrite32(myled_value, base_addr); return count; } /* Callback function when opening file /proc/myled * ------------------------------------------------------ * Read the register value of myled controller, print the value to * the sequence file struct seq_file *p. In file open operation for /proc/myled * this callback function will be called first to fill up the seq_file, * and seq_read function will print whatever in seq_file to the terminal. */ static int proc_myled_show(struct seq_file *p, void *v) { u32 myled_value; myled_value = ioread32(base_addr); seq_printf(p, "0x%x", myled_value); return 0; } /* Open function for /proc/myled * ------------------------------------ * When user want to read /proc/myled (i.e. cat /proc/myled), the open function * will be called first. In the open function, a seq_file will be prepared and the * status of myled will be filled into the seq_file by proc_myled_show function. */ static int proc_myled_open(struct inode *inode, struct file *file) { unsigned int size = 16; char *buf; struct seq_file *m; int res; buf = (char *)kmalloc(size * sizeof(char), GFP_KERNEL); if (!buf) return -ENOMEM; res = single_open(file, proc_myled_show, NULL); if (!res) { m = file->private_data; m->buf = buf; m->size = size; } else { kfree(buf); } return res; } /* File Operations for /proc/myled */ static const struct file_operations proc_myled_operations = { .open = proc_myled_open, .read = seq_read, .write = proc_myled_write, .llseek = seq_lseek, .release = single_release }; /* Shutdown function for myled * ----------------------------------- * Before myled shutdown, turn-off all the leds */ static void myled_shutdown(struct platform_device *pdev) { iowrite32(0, base_addr); } /* Remove function for myled * ---------------------------------- * When myled module is removed, turn off all the leds first, * release virtual address and the memory region requested. */ static int myled_remove(struct platform_device *pdev) { myled_shutdown(pdev); /* Remove /proc/myled entry */ remove_proc_entry(DRIVER_NAME, NULL); /* Release mapped virtual address */ iounmap(base_addr); /* Release the region */ release_mem_region(res->start, remap_size); return 0; } /* Device Probe function for myled * ------------------------------------ * Get the resource structure from the information in device tree. * request the memory regioon needed for the controller, and map it into * kernel virtual memory space. Create an entry under /proc file system * and register file operations for that entry. */ static int myled_probe(struct platform_device *pdev) { struct proc_dir_entry *myled_proc_entry; int ret = 0; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "No memory resource\n"); return -ENODEV; } remap_size = res->end - res->start + 1; if (!request_mem_region(res->start, remap_size, pdev->name)) { dev_err(&pdev->dev, "Cannot request IO\n"); return -ENXIO; } base_addr = ioremap(res->start, remap_size); if (base_addr == NULL) { dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n", (unsigned long)res->start); ret = -ENOMEM; goto err_release_region; } myled_proc_entry = proc_create(DRIVER_NAME, 0, NULL, &proc_myled_operations); if (myled_proc_entry == NULL) { dev_err(&pdev->dev, "Couldn't create proc entry\n"); ret = -ENOMEM; goto err_create_proc_entry; } printk(KERN_INFO DRIVER_NAME " probed at VA 0x%08lx\n", (unsigned long) base_addr); return 0; err_create_proc_entry: iounmap(base_addr); err_release_region: release_mem_region(res->start, remap_size); return ret; } /* device match table to match with device node in device tree */ static const struct of_device_id myled_of_match[] = { {.compatible = "dglnt,myled-1.00.a"}, {}, }; MODULE_DEVICE_TABLE(of, myled_of_match); /* platform driver structure for myled driver */ static struct platform_driver myled_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = myled_of_match}, .probe = myled_probe, .remove = myled_remove, .shutdown = myled_shutdown }; /* Register myled platform driver */ module_platform_driver(myled_driver); /* Module Informations */ MODULE_AUTHOR("Digilent, Inc."); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION(DRIVER_NAME ": MYLED driver (Simple Version)"); MODULE_ALIAS(DRIVER_NAME); |
これをmyled.cとしてdriversディレクトリに保存します。そしたらコンパイル
1 |
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- |
これでmyled.koが出来ると思います。これがデバイスドライバになります。
作成されたものを確認してみましょう。
デバイスツリー
これで最後の作業になります。
LinuxにmyLedのことを教えないといけません。USBはここのアドレスとかのハードウェアに関するものを記述したものがデバイスツリーでそれをコンパイルして一緒に入れる必要があります。ZYBO用のテンプレートはDigilent_Linux_Tutrial/Linux-Digilent-Dev/arch/arm/boot/dts/zynq-zybo.dts
にありますので、これをdriversディレクトリにコピーしましょう。
コピーしたら中身を編集します。http://bravo-fpga.blogspot.jp/2014/10/zybo-7.htmlを参考にします。
まず、チュートリアルで支持されている変更をしますが、上記サイトを元にchosenを
1 2 3 4 5 |
chosen { /* bootargs = "console=ttyPS0,115200 root=/dev/ram rw earlyprintk"; */ bootargs = "console=ttyPS0,115200 root=/dev/ram rw earlyprintk rootwait devtmpfs.mount=1"; linux,stdout-path = "/amba@0/serial@e0001000"; } ; |
このように書き換えます。また、一番下にmyledを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ps7_xadc: ps7-xadc@f8007100 { clocks = <&clkc 12>; compatible = "xlnx,zynq-xadc-1.00.a"; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 7 4>; reg = <0xf8007100 0x20>; } ; myled { compatible = "dglnt,myled-1.00.a"; reg = <0x43c30000 0x10000>; }; } ; } ; |
これでOKです。これをdtcでコンパイルしてdtbというファイルを作ります。
1 |
../Linux-Digilent-Dev/scripts/dtc/dtc -I dts -O dtb -o devicetree.dtb zynq-zybo.dts |
これでdriversディレクトリにdevicetree.dtbが作成されます。
ここまででSDカードに書き込んでLinuxを立ち上げる準備が出来ました。
次回はZynqのLinuxからecho 0x0F > /proc/myled でLEDをつけてみる