2018年11月9日金曜日

[Ubuntu] 在 Ubuntu 18.04 上開發 kernel module


在做 Embedded Linux 開發的大部分時間中

通常很多東西都是平台相依

所以只要沒有板子就沒辦法做任何的測試

但當然也有些東西是不需要板子就可以做測試

所以這篇就是要說

怎麼在 Ubuntu 上進行 kernel module 開發

然後寫好之後進行驗證

當開發平台跟執行平台都是同一個的時候

這意味著這個動作完全不會用到 cross compiler

也就是說如果要驗證交叉編譯的東西的話

基本上這個方法不適用

不過如果只是想做個 kernel module 開發的話

基本上是沒什麼問題才對

所以下面就開始說明大概要做哪些事


首先需要把開發所使用的 Ubuntu 平台變成有編譯 kernel module 的能力

所以需要安裝一些東西


安裝開發工具

sudo apt install build-essential kernel-package libncurses5-dev libelf-dev gcc g++

基本上工具有這些就足夠了

但是還需要安裝 kernel 開發用的 library source

所以還需要再做點事情



安裝 linux-headers


在安裝之前需要先查看一下現在使用的平台的 kernel 版本

apt-cache search linux-headers-$(uname -r)










看起來大概會像上面那張圖一樣

是一個 看起來像是 linux-headers-X.Y.Z-V-M 的格式

通常格式的最後一串英文字表示平台的種類

至於為什麼要這個

是因為接下來的開發會在這個平台上執行跟測試

所以必須先查看這個平台的資訊

不然等等做出來的檔案會沒辦法執行

也就是說如果要在其他平台上執行

至少要先確認要執行的平台的環境才是

檢查完之後接著就是安裝 linux-headers 了

sudo apt install linux-headers-$(uname -r)-generic

這邊我偷懶了

因為直接代入 $(uname -r)

所以其實前面那個版本號查不出來也沒關係


到這邊基本上就完成所以的 kernel module 開發的前置作業了




接下來就可以開始寫 code 了


製作 kernel module


首先隨便找個路徑用來做 kernel module

然後在資料夾下面產生一個 Makefile 和 .c source file

看起來會像下面這張圖這樣



















然後看看 Makefile 裡面長怎麼樣















然後再來看看 source




















基本上這只是個 sample 而已

所以就不要太計較了...OTL

然後就可以編譯它了












沒意外的話

編譯完應該會長的類似上面那張圖一樣

然後就可以看看它產出了什麼東東














正常來說

如果有編譯成功的話

會在路徑下找到一個 .ko 檔

這個就是一個可以搬的走的 kernel module 了



既然寫好了

當然就是把它掛載起來執行看看啦


掛載 kernel module


這邊因為是製作成 loadable 的模組

所以基本上只能使用 insmod 來掛載

因為在 系統的 lib module 中 並不存在這個 module

所以 modprobe 是沒有辦法使用的 ...

然後掛載起來看起來會像下面這樣









敲個密碼

然後什麼事都沒有發生就結束了

所以那剛剛那個 printk 的訊息印到哪去了?



查看 kernel 訊息


這時候就要請出另外一個叫做 dmesg 的指令了













至於這張圖裡面為什麼會有那麼多奇怪的內容

是因為在寫這篇之前我正在幫我的 module debug...

而這篇的範例就是用它改出來的

所以這點小事就不要太計較了XD


最後如果這個 module 不是設計來常駐的話

記得把它卸載

卸載 kernel module


看起來像下面這張圖一樣










一樣什麼訊息都沒顯示

這時候再看一次 dmesg












這時候就會看到離開的訊息了

話說回來

實際上根據不同的 kernel message 等級

其實 dmesg 是會有顏色的



















這樣看起來就清楚多了


然後這篇大概就是這樣了
それでは(・ω・)ノシ


2018年11月8日木曜日

[Ubuntu] 在 Ubuntu 18.04 上安裝多個 gcc/g++ 版本並切換


前一陣子在做開發的時候遇到了一些小問題

就是有時候某些專案所指定的 gcc/g++ 必須使用特定的版本

但不幸的是這些專案都指定必須要使用 host gcc/g++ 的設定

所以就必須要讓多個 gcc/g++ 的版本共存在 host 端

但即便是已經存在了多個版本的時候還是會需要手動切換不同的版本

所以就有這篇了

先檢查看看有沒有 gcc/g++

因為 gcc/g++ 兩個使用的是完全一樣的動作

所以下面偷懶 只用 gcc 舉例

g++ 的部份就把 gcc 換成 g++ 就行了

查看當前 gcc 的版本

gcc -v

如果這樣沒有出現 gcc 的版本訊息的話

表示系統裡不存在 gcc

這時候需要先安裝 gcc/g++

安裝的指令

sudo apt-get install -y gcc-4.8 g++-4.8 gcc-4.9 g++-4.9 gcc-5 g++-5 gcc-6 g++-6 gcc-7 g++-7

這邊安裝的 gcc 版本可以依照自己喜歡的選擇


如果這個時候發現想要的版本抓不下來的時候

也許還有其他選擇

改從其他地方抓

所以這個時候要先增加一個 repository

新增 repo

sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt update


如果這時候你的 gcc/g++ 版本很多很亂

也可以選擇先把它們全部移除

從 update-alternatives 移除所有的 gcc/g++

sudo update-alternatives --remove-all gcc
sudo update-alternatives --remove-all g++

這樣會從 update-alternatives 中移除所有的 gcc/g++ 選項

對 update-alternatives 安裝想要的 gcc/g++

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 10
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 20
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 30
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 40
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 50

sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 20
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 30
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 40
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 50

上面這些版本必須是前面剛剛已經先從 apt install 裝完的各個版本

每個指令最後的那個數字表示預設的優先度

數字越大就越高


設定 預設 gcc/g++ 的版本

最後在安裝完之後就是對 update-alternatives 進行設定

sudo update-alternatives --set cc /usr/bin/gcc
sudo update-alternatives --set c++ /usr/bin/g++

之後要切換版本的時候就只要執行 config 就可以了

切換 預設 gcc/g++ 的版本

sudo update-alternatives --config gcc
sudo update-alternatives --config g++


大概是這樣

2018年11月5日月曜日

[Ubuntu] Ubuntu 18.04 掛載 exFAT 硬碟


最近因為遇到檔案大於 4G 用隨身碟傳輸時候沒辦法用 FAT32
所以只好格式化成 exFAT
結果 Ubuntu 預設竟然沒有辦法讀取...=v=
不過還好裝個工具就解決了

安裝的時候不用先卸載 隨身碟裝置
只需要把資料夾先關掉
是說根本掛載失敗了你也打不開就是了ww
然後安裝完之後
就可以直接打開隨身碟了

安裝的指令

sudo apt install exfat-fuse exfat-utils


就這樣沒了

2018年11月1日木曜日

[Linux] PCIe Endpoint Driver with Character Device Driver


好一陣子沒寫東西了
來紀錄一下最近做的東西

最近從 Windows driver 轉做 Linux driver
不知道是不是找資料的方式不對
還是 Linux 更新太快了
一直都找不到比較新的資料

Linux kernel 都已經更新到 4.19 了
結果大部分的資料都還在講 2.6.x
雖然絕大部分都可以向下相容
但總是有些東西沒辦法用

所以這篇用來紀錄一下
前陣子用 kernel 4.x 做 PCIe EP device driver 開發的東西
說是紀錄
但也就只是放個 pcie 的 device driver 的樣板上來而已
本來偷懶想直接全部複製貼上
不過因為有很多東西不能公開
所以只好貼通用的部份上來了

是說完整的內容太長了
所以還是分段貼好了...OTL

理論上下面這串程式直接做成一個.c檔
建成一個 kernel module 應該就可以用了
但不保證我手殘有很大的機會敲錯就是了...w



#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/init.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_pci.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/phy/phy.h>

#include <linux/irqreturn.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/dma-mapping.h>
#include <linux/wait.h>
#include <linux/completion.h>


#ifndef PCI_EP_ENGINE_DRIVER_VERSION_NUMBER
#define PCI_EP_ENGINE_DRIVER_VERSION_NUMBER "1.0"
#endif

#ifndef PCI_EP_ENGINE_NAME
#define PCI_EP_ENGINE_NAME "pcie_ep_device"
#endif

#ifndef PCI_EP_ENGINE_VENDOR_ID
/* 0x11ab for Marvell Technology Group Ltd.*/
/* ref. http://pci-ids.ucw.cz/read/PC */
#define PCI_EP_ENGINE_VENDOR_ID 0x11ab 
#endif

#ifndef PCI_EP_ENGINE_DEVICE_ID
/* 0x0023 for Marvell 88pa6220 */
#define PCI_EP_ENGINE_DEVICE_ID 0x0023
#endif

#ifndef PCI_EP_ENGINE_CLASS_ID
/* 0x08: Generic system peripheral */
/* 0xXX80: System peripheral */
/* 0xXXXX00: Program interfaces */
#define PCI_EP_ENGINE_CLASS_ID 0x088000
#endifux

#ifndef PCI_EP_ENGINE_CLASS_MASK
/* i am not sure what this is ... */
#define PCI_EP_ENGINE_CLASS_MASK 0xffffff00
#endif

// 0: for static; 1: for dynamic
#define PCI_EP_ENGINE_DYNAMIC_ALLOC_CHR_NUM 1

#ifndef PCI_EP_ENGINE_ALLOC_CHR_COUNT
#define PCI_EP_ENGINE_ALLOC_CHR_COUNT 1
#endif

#ifndef PCI_EP_ENGINE_CHAR_MAJOR
#define PCI_EP_ENGINE_CHAR_MAJOR 0
#endif

#ifndef PCI_EP_ENGINE_CHAR_MINOR
#define PCI_EP_ENGINE_CHAR_MINOR 0
#endif

#define PCI_EP_ENGINE_FUNCTION_IN_PRINT \
    printk(KERN_ALERT "%s(%d): in\n", __func__, __LINE__)

#define PCI_EP_ENGINE_FUNCTION_OUT_PRINT \
    printk(KERN_ALERT "%s(%d): out\n", __func__, __LINE__)

#define PCI_EP_ENGINE_PRINT_FUNCTION_CALLER \
    printk(KERN_ALERT "Caller is: %pS\n", __builtin_return_address(0))

static struct class * g_p_pci_ep_engine_class;
static struct device * g_p_pci_ep_engine_device;

static struct pci_device_id pci_ep_engine_ids[] = {
//{PCI_EP_ENGINE_VENDOR_ID, PCI_EP_ENGINE_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, /* how to determine the RC or EP with same VID & DID ? */
{ PCI_DEVICE_CLASS(PCI_EP_ENGINE_CLASS_ID, PCI_EP_ENGINE_CLASS_MASK) }, /* determine the EP which is a System peripheral */
{0,}
};
MODULE_DEVICE_TABLE(pci, pci_ep_engine_ids);

struct pci_ep_engine
{
    struct pci_device * pci_dev;
    struct cdev chr_dev;
    dev_t devno;
    unsigned long bar0_physical;
    unsigned long bar0_virtual;
    unsigned long bar0_length; 
} ep_engine_device;

static int pci_ep_engine_probe(struct pci_dev * pdev, const struct pci_device_id * id);
static void pci_ep_engine_remove(struct pci_dev * pdev);
static irqreturn_t pci_ep_engine_interrupt(int irq, void * pdev);

static int pci_ep_engine_probe(struct pci_dev * pdev, const struct pci_device_id * id)
{
    int i;
    int result;
    PCI_EP_ENGINE_FUNCTION_IN_PRINT; 
    pci_set_drvdata(pdev, ep_engine_device.pci_dev);

    if (pci_enable_device(pdev)) {
        PCI_EP_ENGINE_DEBUG_ERR("failed' pci_enable_device\n");
        result = -EIO;
        goto end;
    }

    pci_set_master(pdev);
    ep_engine_device.pci_dev = pdev;

    if (unlikely(pci_request_regions(pdev, PCI_EP_ENGINE_NAME))) {
        printk(KERN_ALERT "%s(%d): failed pci_request_regions\n", __func__, __LINE__);
        result = -EIO;
        goto enable_device_err;
    }

    // get bar 0 physical & virtual address
    ep_engine_device.bar0_physical = pci_resource_start(pdev, 0);
    if (ep_engine_device.bar0_physical < 0) {
        printk(KERN_ALERT "%s(%d): failed: pci_resource_start\n", __func__, __LINE__);
        result = -EIO;
        goto request_regions_err;
    } else {
        printk(KERN_ALERT "%s(%d): got pci resource: bar 0 physical address(0x%08x)\n", __func__, __LINE__, ep_engine_device.bar0_physical);
    }

    // get bar 0 virtual address
    ep_engine_device.bar0_length = pci_resource_len(pdev, 0);
    if (ep_engine_device.bar0_length != 0) {
        ep_engine_device.bar0_virtual = (unsigned long)ioremap(ep_engine_device.bar0_physical, ep_engine_device.bar0_length);
        printk(KERN_ALERT "%s(%d): bar 0 physical remap to virt: 0x%08x\n", __func__, __LINE__, ep_engine_device.bar0_virtual);
        printk(KERN_ALERT "%s(%d): bar 0 length: %lu\n", __func__, __LINE__, ep_engine_device.bar0_length);
    } else {
        printk(KERN_ALERT "%s(%d): failed: pci_resource_len get zero!!\n", __func__, __LINE__);
    }

    // enable MSI
    result = pci_enable_msi(pdev);
    if (unlikely(result)) {
        printk(KERN_ALERT "%s(%d): failed: pci_enable_msi\n", __func__, __LINE__);
        goto free_bar0;
    } else {
        printk(KERN_ALERT "%s(%d): enable msi succeeded.\n", __func__, __LINE__);
    }

    result = request_irq(pdev->irq, pci_ep_engine_interrupt, 0, PCI_EP_ENGINE_NAME, ep_engine_device.pci_dev);
    if (unlikely(result)) {
        printk(KERN_ALERT "%s(%d): failed: request_irq\n", __func__, __LINE__);
        goto enable_msi_error;
    } else {
        printk(KERN_ALERT "%s(%d): request_irq succeeded.\n", __func__, __LINE__);
    }

    goto end;
  
enable_msi_error:
    pci_disable_msi(pdev);
free_bar0:
    iounmap((void*)ep_engine_device.bar0_virtual);
request_regions_err:
    pci_release_regions(pdev);
enable_device_err:
    pci_disable_device(pdev);
end:
    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
    return result;   
}

static void pci_ep_engine_remove(struct pci_dev * pdev)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;

    free_irq(pdev->irq, ep_engine_device.pci_dev);
    pci_disable_msi(pdev);

    iounmap((void*)ep_engine_device.bar0_virtual);
    pci_release_regions(pdev);
    pci_disable_device(pdev);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
}

static irqreturn_t pci_ep_engine_interrupt(int irq, void * dev)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;
  
    // do something at here 

    PCI_VIRTIO_ENGINE_FUNCTION_OUT_PRINT;
    return IRQ_HANDLED;
}

static struct pci_driver pci_ep_engine_driver = {
    .name = PCI_EP_ENGINE_NAME,
    .id_table = pci_ep_engine_ids,
    .probe = pci_ep_engine_probe,
    .remove = pci_ep_engine_remove,
};

static int pci_ep_engine_open(struct inode * inode, struct file * file)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

int pci_ep_engine_close(struct inode * inode, struct file * file)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

long pci_ep_engine_unlocked_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static ssize_t pci_ep_engine_read(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static ssize_t pci_ep_engine_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static struct file_operations pci_ep_engine_fops = {
    .owner = THIS_MODULE,
    .open = pci_ep_engine_open,
    .release = pci_ep_engine_close,
    .unlocked_ioctl = pci_ep_engine_unlocked_ioctl,
    .read = pci_ep_engine_read,
    .write = pci_ep_engine_write,
};

static int pci_ep_engine_driver_init(void)
{
    int ret;
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;
    PCI_Ep_ENGINE_PRINT_FUNCTION_CALLER;
    
    ret = pci_register_driver(&pci_ep_engine_driver);
    if (ret < 0) {
        printk("failed: pci_register_driver\n");
        return ret;
    }

#if    PCI_EP_ENGINE_DYNAMIC_ALLOC_CHR_NUM
    ret = alloc_chrdev_region(&ep_engine_device.devno, 0, PCI_EP_ENGINE_ALLOC_CHR_COUNT, PCI_EP_ENGINE_NAME);        
#else
    ep_engine_device.devno = MKDEV(PCI_EP_ENGINE_CHAR_MAJOR, PCI_EP_ENGINE_CHAR_MINOR);
    ret = register_chrdev_region(ep_engine_device.devno, PCI_EP_ENGINE_CHAR_MINOR, PCI_EP_ENGINE_ALLOC_CHR_COUNT, PCI_EP_ENGINE_NAME);
#endif
    if (ret < 0) {
        printk("failed: register_chrdev_region\n");
        return ret;
    }

    cdev_init(&ep_engine_device.chr_dev, &pci_ep_engine_fops);
    ret = cdev_add(&ep_engine_device.chr_dev, ep_engine_device.devno, PCI_EP_ENGINE_ALLOC_CHR_COUNT);
    if (ret < 0) {
        printk("failed: cdev_add\n");
        return ret;
    }

    g_p_pci_ep_engine_class = class_create(THIS_MODULE, PCI_EP_ENGINE_NAME);
    g_p_pci_ep_engine_device = device_create(g_p_pci_ep_engine_class, NULL, ep_engine_device.devno, NULL , PCI_EP_ENGINE_NAME);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
    return 0;
}

static void pci_ep_engine_driver_exit(void)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;

    device_destroy(g_p_pci_ep_engine_class, ep_engine_device.devno);
    class_destroy(g_p_pci_ep_engine_class);

    cdev_del(&(ep_engine_device.chr_dev));
    unregister_chrdev_region(ep_engine_device.devno, PCI_EP_ENGINE_ALLOC_CHR_COUNT);
    pci_unregister_driver(&pci_ep_engine_driver);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
}

module_init(pci_ep_engine_driver_init);
module_exit(pci_ep_engine_driver_exit);

MODULE_AUTHOR("yuutan");
MODULE_LICENSE("GPL");
MODULE_VERSION(PCI_EP_ENGINE_DRIVER_VERSION_NUMBER);
MODULE_DESCRIPTION("ep engine over PCIe: device driver with character device driver");