• 8. Zynq デバイスドライバとデバイスツリーを作る

    デバイスドライバは、今回作ったPL部を読み書きするためのものです。Linuxではダイレクトにメモリアクセスするのも出来ますが、チュートリアルに従いデバイスドライバを書きます。

    Makefile

    まず作業するディレクトリですが、digilentディレクトリに作ります

    [shell]

    mkdir drivers
    cd drivers

    [/shell]

    あとはここにMakefileという名前のファイルを作って

    [shell]

    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

    [/shell]

    を書き込みます。

    注意点

    • 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つ追加しています。

    [c]
    #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);
    [/c]

    これをmyled.cとしてdriversディレクトリに保存します。そしたらコンパイル

    [shell]

    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

    [/shell]

    これでmyled.koが出来ると思います。これがデバイスドライバになります。
    作成されたものを確認してみましょう。

    Screen Shot 2015-01-11 at 14.21.24

    デバイスツリー

    これで最後の作業になります。
    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を

    [shell]

    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”;
    } ;

    [/shell]

    このように書き換えます。また、一番下にmyledを追加します。

    [shell]

    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>;
    };
    } ;
    } ;

    [/shell]

    これでOKです。これをdtcでコンパイルしてdtbというファイルを作ります。

    [shell]

    ../Linux-Digilent-Dev/scripts/dtc/dtc -I dts -O dtb -o devicetree.dtb zynq-zybo.dts

    [/shell]

    これでdriversディレクトリにdevicetree.dtbが作成されます。

    Screen Shot 2015-01-11 at 14.33.30

    ここまででSDカードに書き込んでLinuxを立ち上げる準備が出来ました。
    次回はZynqのLinuxからecho 0x0F > /proc/myled でLEDをつけてみる

  • 7. Zynq RAMディスク

    RAMディスクは公開されている

    https://digilentinc.com/Data/Products/EMBEDDED-LINUX/ramdisk8M.image.gz

    これをダウンロードしてきてUbuntuのdigilentディレクトリに保存する。

    Screen Shot 2015-01-11 at 10.47.43
    そこで

    [shell]

    sudo ./u-boot-Digilent-Dev/tools/mkimage -A arm -T ramdisk -c gzip -d ./ramdisk8M.image uramdisk.image.gz

    [/shell]

    これを実行したら終わり。出てきたuramdisk.image.gzが必要なやつです。

    Screen Shot 2015-01-11 at 10.58.19

    次は  デバイスドライバとデバイスツリーを作る

  • 6. Zynq VivadoなしのUbuntuでZynq向けLinuxカーネルをビルドする

    Linuxのコンパイルは簡単です。さっきのU-bootをcloneした digilentディレクトリを開いて下さい、開いたら

    [shell]

    sudo git clone -b master-next https://github.com/DigilentInc/Linux-Digilent-Dev.git
    cd Linux-Digilent-Dev/
    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- xilinx_zynq_defconfig
    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- UIMAGE_LOADADDR=0x8000 uImage

    [/shell]

    これで終わりです。Linux-Digilent-Dev.git/arc/arm/bootにuImageというのができます。

    Screen Shot 2015-01-11 at 03.07.55

    これが欲しかったLinuxカーネルです。簡単ですが、コンパイルの時間だけはそれなりに(僕の手元だと10~20分)かかります。


    メモ

    ちなみに

    [shell]

    BC kernel/timeconst.h
    /bin/sh: 1: bc: not found
    make[1]: *** [kernel/timeconst.h] Error 127
    make: *** [kernel] Error 2

    [/shell]

    こんなエラーが出る場合はただ、bcってのがないだけなので
    sudo apt-get install bc
    を実行してからやり直して下さい。

    次は RAMディスク

     

  • 5. Zynq SDKでFSBLを作る

    ここまでで、回路構成やbitstream、それにLinuxを起動するためのブートローダであるU-Bootを作成しました。
    あとはFSBLと一緒にしてBOOT.binを作るだけです。

    SDK

    SDKを起動します。前回のVivadoのプロジェクトから起動すると楽ちんです。
    「File」->「Launch SDK」

    lb58

    Locationとworkspaceを聞かれますが、そのままで大丈夫です。

    lb59

    OKを押すとSDKが起動すると思います。

    lb60

    まずFSBLが必要です。
    「File」->「New」->「Application Project」でプロジェクトの新規作成します
    lb63

    プロジェクトの名前は適当に「myLED_FSBL」としました。(なんでもいいです)

    lb64

    ここで、Finishを押さずNextを押します。

    lb65

    テンプレートからFSBLを選択し、Finishします。
    問題なくFSBLのプロジェクトが追加されていますね。

    lb66

    FSBLを編集する

    hooks.cを編集する必要があります。zybo_base_systemの中にあるので、それをまるごとコピペして利用します。
    場所はzybo_base_system>source>vivado>SDK>fsblです。
    lb67

    これをなんでもいいのでエディタで開いて中身をコピーし、それでsdkのfsbl_hooks.cを上書きします。
    保存するとすぐにコンパイルが行われますので、なんかおかしかったらエラーになります。
    成功するとFSBL.elfが作成されます。

    lb68

    いよいよBOOT.binの作成

    プロジェクトフォルダを右クリックし、「Create Boot Image」を押します。

    lb69

    恐らく最初の段階でこんなふうになっていると思います。
    したの「Boot image partisions」がBOOT.binに含まれるものになります。
    既に入っているのはFSBLとbitstreamです。
    ここでやらなきゃいけないのはUbuntuで頑張って作ったu-boot.elfを追加することです。

    lb70

    右側の「Add」を押します。

    lb71
    Browseでファイルを選択します。

    lb73

    選択したら「開く」

    lb74

    これでu-bootがpartisionの3つ目に追加されました。
    「Create Image」を押すと「Output path」に書かれているフォルダ(そのままだと、 zebu_base_system/source/vivado/hw/zybo_bsd.sdk/myLED_FSBL/bootimage¥BOOT.bin)にBOOT.binが作成されます。

    このBOOT.binがSDカードの中では最初に呼ばれるファイルです。
    そしてFSBLが最初に起動し、bitstreamでFPGAをコンフィグレーションし、u-bootを起動します。
    そして、u-bootはこれから作るLinuxカーネルを起動するのです。

    次はこちら VivadoなしのUbuntuでZynq向けLinuxカーネルをビルドする

  • 4. Zynq VivadoなしのUbuntuでGCCだけでU-Bootを作る

    ここからはUbuntuになります。
    前に話した通りvagrantがオススメです。

    今回何をするかというと、U-Bootというブートローダーを作ります。
    そもそも今はBOOT.BINの作成をしていて、前回まででbitstreamは出来ました、あとはUbuntu上でU-Bootというブートローダーを作り、XilinxのSDKでFSBLと一緒にBOOT.BINにしちゃうってのをやります。

    Ubuntu起動

    [shell]

    vagrant up
    vagrant ssh

    [/shell]

    で、sshで入りました。VirtualBoxのGUIでやってる人は「端末」を起動すればOKです。</pre>
    <h2>必要な物を入れる</h2>
    <pre>
    ARMで動くプログラムをARMじゃないパソコンで作らないといけません。つまりクロスコンパイルです。gccのarmのクロスコンパイル環境を作ります。

    [shell]
    sudo apt-get -f install
    sudo apt-get install fakeroot build-essential kexec-tools
    sudo apt-get install kernel-wedge gcc-arm-linux-gnueabi
    sudo apt-get install uboot-mkimage libncurses5
    sudo apt-get install libncurses5-dev libelf-dev
    sudo apt-get install asciidoc binutils-dev git
    sudo apt-get build-dep linux
    sudo sudo apt-get install bc
    [/shell]

    これで環境は整いました。ホームディレクトリに今回の作業ディレクトリを作り、Xilinxの出しているU-bootのプログラムをcloneしてきます

    [shell]

    cd ~
    mkdir digilent
    cd digilent
    sudo git clone -b master-next https://github.com/DigilentInc/u-boot-Digilent-Dev.git

    [/shell]

    あとはcloneしてきたディレクトリに入りmakeするだけで終わりです。

    [shell]

    cd u-boot-Digilent-Dev
    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zynq_zybo_config
    sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

    [/shell]

    これでu-bootという拡張子のないファイルが作成されるはずです。

    Screen Shot 2015-01-11 at 01.49.04

    できたu-bootをvivadoのプロジェクトフォルダに入れてしまい.elfという拡張子を付けてしまいましょう。
    zybo_base_system/sd_imageにいれます。

    lb73

    これで完了。
    このu-bootは基本的には回路に関係ないので、今後もこのファイルを使いまわせます。今回だけですね、消さずに残しておきましょう。

    次は 5. SDKでFSBLを作る