2016年1月2日 星期六

Linux Kernel(15)- Platform Devices


很多人心中都有過一個問題What is the difference between Platform driver and normal device driver?,簡單的來說Platform devices就non-discoverable,也就是device本身沒辦法跟系統說"我在這裡",典型的就是I2C device,它不會通知kernel"我在這裡",通常是預先知道有個I2C device在那裡,再由software設定好,這類non-discoverable device就適用Platform devices架構來寫。
platform device會被connect在platform bus上,而platform bus是一個虛擬的bus(pseudo-bus),這樣可以讓整個架構platform driver符合Linux的標準driver model。

這篇會根據The platform device API教導如何寫一個簡單的Platform devices,基本上最基本的platform device只需要name,因為platform bus會根據platform device與platform driver的name是否match執行driver的probe(),而最簡單的platform driver只需要name,跟probe()與remove()即可。
#include <linux/module.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module for demo");
MODULE_LICENSE("GPL");

#define DEVNAME "brook"

#define DYN_ALLOC 1

static struct platform_device brook_device = {
    .name = DEVNAME,
};

static int brook_probe(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static int brook_remove(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static struct platform_driver brook_driver = {
    .driver = {
        .name  = DEVNAME,
        .owner = THIS_MODULE,
    },
    .probe  = brook_probe,
    .remove = brook_remove,
};

static int __init brook_init(void)
{
    int err;
    pr_info("%s(#%d)\n", __func__, __LINE__);

    err = platform_device_register(&brook_device);
    if (err) {
        pr_err("%s(#%d): platform_device_register failed(%d)\n",
                __func__, __LINE__, err);
        return err;
    }

    err = platform_driver_register(&brook_driver);
    if (err) {
        dev_err(&(brook_device.dev), "%s(#%d): platform_driver_register fail(%d)\n",
                __func__, __LINE__, err);
        goto dev_reg_failed;
    }
    return err;

dev_reg_failed:
    platform_device_unregister(&brook_device);

    return err;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
    dev_info(&(brook_device.dev), "%s(#%d)\n", __func__, __LINE__);
    platform_device_unregister(&brook_device);
    platform_driver_unregister(&brook_driver);
}
module_exit(brook_exit);


使用platform_device_register()會導致"brook.0" does not have a release() function, it is broken and must be fixed.的OOPS,可以改用platform_device_alloc() + platform_device_add(),platform_device_alloc()裡面就會做pa->pdev.dev.release = platform_device_release。
#include <linux/module.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module for demo");
MODULE_LICENSE("GPL");

#define DEVNAME "brook"

#define DYN_ALLOC 1

static struct platform_device *brook_device;

static int brook_probe(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static int brook_remove(struct platform_device *pdev)
{
    pr_info("%s(#%d)\n", __func__, __LINE__);
    return 0;
}

static struct platform_driver brook_driver = {
    .driver = {
        .name  = DEVNAME,
        .owner = THIS_MODULE,
    },
    .probe  = brook_probe,
    .remove = brook_remove,
};

static int __init brook_init(void)
{
    int err;
    pr_info("%s(#%d)\n", __func__, __LINE__);

    /* using platform_device_alloc() + platform_device_add() 
     * instead of platform_device_register() to avoid the OOPS, 
     *     "Device 'brook.0' does not have a release() function,
     *      it is broken and must be fixed."
     */
    brook_device = platform_device_alloc(DEVNAME, 0);
    if (!brook_device) {
        pr_err("%s(#%d): platform_device_alloc fail\n",
               __func__, __LINE__);
        return -ENOMEM;
    }

    err = platform_device_add(brook_device);
    if (err) {
        pr_err("%s(#%d): platform_device_add failed\n",
               __func__, __LINE__);
        goto dev_add_failed;
    }

    err = platform_driver_register(&brook_driver);
    if (err) {
        dev_err(&(brook_device->dev), "%s(#%d): platform_driver_register fail(%d)\n",
                __func__, __LINE__, err);
        goto dev_reg_failed;
    }
    return err;

dev_add_failed:
    platform_device_put(brook_device);
dev_reg_failed:
    platform_device_unregister(brook_device);

    return err;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
    dev_info(&(brook_device->dev), "%s(#%d)\n", __func__, __LINE__);
    platform_device_unregister(brook_device);
    platform_driver_unregister(&brook_driver);
}
module_exit(brook_exit);



    參考資料:
  1. Documentation/driver-model
  2. What is the difference between Platform driver and normal device driver?
  3. The platform device API
  4. Linux Kernel architecture for device drivers
  5. platform_driver_register()--如何match之后调用probe
  6. Improved dynamically allocated platform_device interface




2015年12月26日 星期六

OpenEmbedded -- Creating and using an SDK


SDK包含了開發時所需的相關檔案,有些還包含了整個開發環境(tool/command),避免系統的tool/command跟SDK需要的不同造成的error,一個SDK應該包含以下內容:(YOCTO Slides)
  1. Compilers or cross-compilers
  2. Linkers
  3. Library headers
  4. Debuggers
  5. Custom utilities


這篇文章就是在介紹如何使用Openembedded建立一個generic SDK供他人開發application,使用指令bitbake -v meta-toolchain即可產生generic SDK,並產生一個install的script,放置於oe/oe-core/build/tmp-eglibc/deploy/sdk/底下,執行過程如下
brook@vista:/home/brook/oe/oe-core/build$ bitbake -v meta-toolchain

...

+ chmod +x /home/brook/oe/oe-core/build/tmp-eglibc/deploy/sdk/oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh

+ cat /home/brook/oe/oe-core/build/tmp-eglibc/deploy/sdk/oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.tar.bz2

+ rm /home/brook/oe/oe-core/build/tmp-eglibc/deploy/sdk/oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.tar.bz2


NOTE: Tasks Summary: Attempted 1685 tasks of which 1312 didn't need to be rerun and all succeeded.
brook@vista:/home/brook/oe/oe-core/build$ tree tmp-eglibc/deploy/sdk/
tmp-eglibc/deploy/sdk/
`-- oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh

0 directories, 1 file

接著直要將底下的script(oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh)給developer開發即可,-d後面帶安裝目錄,安裝過程需要SUDO權限,
brook@vista:/home/brook/oe/oe-core/build$ ./tmp-eglibc/deploy/sdk/oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh --help
Usage: oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh [-y] [-d <dir>]
  -y         Automatic yes to all prompts
  -d <dir>   Install the SDK to <dir>
======== Advanced DEBUGGING ONLY OPTIONS ========
  -S         Save relocation scripts
  -R         Do not relocate executables
  -D         use set -x to see what is going on
brook@vista:~$ /home/brook/oe/oe-core/build/tmp-eglibc/deploy/sdk/oecore-x86_64-armv7a-vfp-neon-toolchain-oe-core.0.sh -d /opt/oe -D
+ printf 'Enter target directory for SDK (default: /usr/local/oecore-x86_64): '
Enter target directory for SDK (default: /usr/local/oecore-x86_64): + '[' /opt/oe = '' ']'
+ echo /opt/oe
/opt/oe

...

+ '[' 0 = 0 ']'
+ /usr/bin/sudo rm /SSD/opt/oe/relocate_sdk.py /SSD/opt/oe/relocate_sdk.sh
+ echo 'SDK has been successfully set up and is ready to be used.'
SDK has been successfully set up and is ready to be used.
+ exit 0

要使用SDK之前,必須先source安裝目錄下開頭為environment-setup-的script,設置相關的環境變數,我的script內容如下
export PATH=/SSD/opt/oe/sysroots/x86_64-oesdk-linux/usr/bin:/SSD/opt/oe/sysroots/x86_64-oesdk-linux/usr/bin/armv
7a-vfp-neon-oe-linux-gnueabi:$PATH
export PKG_CONFIG_SYSROOT_DIR=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi
export PKG_CONFIG_PATH=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi/usr/lib/pkgconfig
export CONFIG_SITE=/SSD/opt/oe/site-config-armv7a-vfp-neon-oe-linux-gnueabi
export CC="arm-oe-linux-gnueabi-gcc  -march=armv7-a -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mthumb-inte
rwork --sysroot=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export CXX="arm-oe-linux-gnueabi-g++  -march=armv7-a -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mthumb-int
erwork --sysroot=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export CPP="arm-oe-linux-gnueabi-gcc -E  -march=armv7-a -mthumb-interwork -mfloat-abi=softfp -mfpu=neon -mthumb-interwork --sysroot=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export AS="arm-oe-linux-gnueabi-as "
export LD="arm-oe-linux-gnueabi-ld  --sysroot=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export GDB=arm-oe-linux-gnueabi-gdb
export STRIP=arm-oe-linux-gnueabi-strip
export RANLIB=arm-oe-linux-gnueabi-ranlib
export OBJCOPY=arm-oe-linux-gnueabi-objcopy
export OBJDUMP=arm-oe-linux-gnueabi-objdump
export AR=arm-oe-linux-gnueabi-ar
export NM=arm-oe-linux-gnueabi-nm
export M4=m4
export TARGET_PREFIX=arm-oe-linux-gnueabi-
export CONFIGURE_FLAGS="--target=arm-oe-linux-gnueabi --host=arm-oe-linux-gnueabi --build=x86_64-linux --with-libtool-sysroot=/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export CFLAGS=" -O2 -fexpensive-optimizations -frename-registers -fomit-frame-pointer"
export CXXFLAGS=" -O2 -fexpensive-optimizations -frename-registers -fomit-frame-pointer -fpermissive"
export LDFLAGS="-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed"
export CPPFLAGS=""
export OECORE_NATIVE_SYSROOT="/SSD/opt/oe/sysroots/x86_64-oesdk-linux"
export OECORE_TARGET_SYSROOT="/SSD/opt/oe/sysroots/armv7a-vfp-neon-oe-linux-gnueabi"
export OECORE_ACLOCAL_OPTS="-I /SSD/opt/oe/sysroots/x86_64-oesdk-linux/usr/share/aclocal"
export OECORE_DISTRO_VERSION="20151225"
export OECORE_SDK_VERSION="oe-core.0"
export PYTHONHOME=/SSD/opt/oe/sysroots/x86_64-oesdk-linux/usr
export ARCH=arm
export CROSS_COMPILE=arm-oe-linux-gnueabi-


以下是我用這SDK編譯一個檔案,並放入target中執行
brook@vista:~$ source /opt/oe/environment-setup-armv7a-vfp-neon-oe-linux-gnueabi
brook@vista:~$ cat main.c
#include <stdio.h>

int main(int argc, char *argv[])
{
        printf("hello\n");
        return 0;
}

brook@vista:~$ $CC -o brook-sdk main.c

D:\Projects>adb push z:\brook-sdk /
688 KB/s (5644 bytes in 0.008s)

D:\Projects>adb shell chmod +x /brook-sdk

D:\Projects>adb shell /brook-sdk
hello

開發環境架構圖


    參考資料:
  1. Yocto Project and OpenEmbedded development course
  2. Yocto Project Quick Start -- SDK Generation





2015年12月19日 星期六

OpenEmbedded - Writing Meta Data for adding Kernel Module package


OpenEmbedded User Manual - CH3, Writing Meta Data (Adding packages)之後,這一篇是教導如何新增Kernel Module package。 BB檔的寫法與OpenEmbedded User Manual - CH3, Writing Meta Data (Adding packages)雷同,只是我們這次iherit的class是module

/oe-core/meta/recipes-kernel/brook/brook_1.0.bb

inherit module
DESCRIPTION = "Brook Out Tree Kernel Module"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://LICENSE;md5=5ff2bd2dd80c7cc542a6b8de3ee4ff87"

PR = "r1-${KERNEL_VERSION}"

# This DEPENDS is to serialize kernel module builds
DEPENDS = "virtual/kernel"


SRC_URI = "file://brook.patch \
"

S = "${WORKDIR}/brook-linux-${PV}"

這邊的SRC_URI我是使用patch格式,當然你也可以使用zip格式,但是zip格式需要考慮目錄名稱必須為"${WORKDIR}/brook-linux-${PV}",即bb檔中的S,但是如果是patch就不用管S目錄名稱為何了。

完整檔案結構如下

後面會陸續介紹每個檔案
    /oe-core/meta/recipes-kernel/brook
    |-- brook_1.0.bb
    |-- files
    |   `-- brook.patch
    `-- git
        |-- LICENSE
        |-- Makefile
        `-- main.c


Makefile

ifneq ($(KERNELRELEASE),)
# obj-m := <module_name>.o
# <module_name>-y := <src1>.o <src2>.o ...

        EXTRA_CFLAGS += -DYAFFS_OUT_OF_TREE

        obj-m := brook.o

        brook-objs := main.o
else
        PWD := $(shell pwd)

modules:
 $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules

modules_install:
 $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules_install

clean:
 $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
endif

這是一般的Kernel Module寫法,沒有特別的地方,Out Tree build時KERNELRELEASE為空字串,所以會用下面那段。

License的內容就只有"License: GPL"這個字串

main.c

Module的主程式,只是在init與exit時印出簡單字串而已。
#include <linux/module.h>

MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module for demo");
MODULE_LICENSE("GPL");

static int __init brook_init(void)
{
        printk(KERN_INFO "Brook Module Init\n");
        return 0;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
        printk(KERN_INFO "Brook Module Exit\n");
        return;
}
module_exit(brook_exit);


brook.patch

這是我把檔案全部放在git目錄底下,做source control,並用git format-patch產生的,將產生後的檔案放置files/brook.patch。




熱門文章