--- tags: LINUX KERNEL --- ##### [回到首頁](https://hackmd.io/@SquirrelPanda/r15DilTMD) # AHCI driver slide: https://hackmd.io/@SquirrelPanda/SJpaiWpzv {%hackmd ryr9Ug6sd %} --- ## 程式碼解析 ### AHCI Start Port - 用於 :::warning :warning: libahci.c 中有兩個相似的 function, 分別是 ahci_port_start() 及 ahci_start_port(), 前者是設定與檢查全部相關設定, 同時也 ACHI HBA 的進入點, 後者則是針對某個 AHCI SATA Port 做設定, 關係大略可以畫成以下: ::: ```graphviz digraph AHCI_Port{ graph [rankdir=LR] nodesep=1.0 node [color=Red,fontname=Courier,shape=box] edge [arrowhead=vee arrowsize=1] ahci_port_start->ahci_port_resume->ahci_start_port } ``` ```c=848 static void ahci_start_port(struct ata_port *ap) { struct ahci_host_priv *hpriv = ap->host->private_data; struct ahci_port_priv *pp = ap->private_data; struct ata_link *link; struct ahci_em_priv *emp; ssize_t rc; int i; /* enable FIS reception */ ahci_start_fis_rx(ap); /* enable DMA */ if (!(hpriv->flags & AHCI_HFLAG_DELAY_ENGINE)) ahci_start_engine(ap); /* turn on LEDs */ if (ap->flags & ATA_FLAG_EM) { ata_for_each_link(link, ap, EDGE) { emp = &pp->em_priv[link->pmp]; /* EM Transmit bit maybe busy during init */ for (i = 0; i < EM_MAX_RETRY; i++) { rc = ahci_transmit_led_message(ap, emp->led_state, 4); if (rc == -EBUSY) ata_msleep(ap, 1); else break; } } } if (ap->flags & ATA_FLAG_SW_ACTIVITY) ata_for_each_link(link, ap, EDGE) ahci_init_sw_activity(link); } ``` ### Power up: ``` c=745 static void ahci_power_up(struct ata_port *ap) { struct ahci_host_priv *hpriv = ap->host->private_data; void __iomem *port_mmio = ahci_port_base(ap); u32 cmd; cmd = readl(port_mmio + PORT_CMD) & ~PORT_CMD_ICC_MASK; /* spin up device */ if (hpriv->cap & HOST_CAP_SSS) { cmd |= PORT_CMD_SPIN_UP; writel(cmd, port_mmio + PORT_CMD); } /* wake up link */ writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); } ``` - Read PxCMD 排除電源管理界面(PxCMD.ICC) - 根據 PxCMD.ICC 的說明, 如果 Link layer 當前狀態為 L_IDLE 或 L_NoCommPower, 則設定ICC有效, 否則一律無視, 因此不用特別對此設定(ICC)預先做處理. - 是否支援分時啟動 (HOST_CAP_SSS) - Set PxCMD.SUD to 1 - 預期是 PxCMD.SUD 由 0 -> 1, MODE : Spin-Up, btidge 發出 COMRESET - Wake up link ( PORT_CMD_ICC_ACTIVE ) ### AHCI engine: #### ahci_start_engine ```c=661 void ahci_start_engine(struct ata_port *ap) { void __iomem *port_mmio = ahci_port_base(ap); u32 tmp; /* start DMA */ tmp = readl(port_mmio + PORT_CMD); tmp |= PORT_CMD_START; writel(tmp, port_mmio + PORT_CMD); readl(port_mmio + PORT_CMD); /* flush */ } EXPORT_SYMBOL_GPL(ahci_start_engine); ``` - 使用到的地方: - ahci_start_port() - ahci_hardreset() - ahci_error_handler() - ahci_set_aggressive_devslp() - ahci_enable_fbs() - ahci_disable_fbs() #### ahci_stop_engine ```c=674 int ahci_stop_engine(struct ata_port *ap) { void __iomem *port_mmio = ahci_port_base(ap); u32 tmp; tmp = readl(port_mmio + PORT_CMD); /* check if the HBA is idle */ if ((tmp & (PORT_CMD_START | PORT_CMD_LIST_ON)) == 0) return 0; /* setting HBA to idle */ tmp &= ~PORT_CMD_START; writel(tmp, port_mmio + PORT_CMD); /* wait for engine to stop. This could be as long as 500 msec */ tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500); if (tmp & PORT_CMD_LIST_ON) return -EIO; return 0; } EXPORT_SYMBOL_GPL(ahci_stop_engine); ``` - 使用到的地方: - ahci_deinit_port() - ahci_kick_engine() - ahci_hardreset() - ahci_error_handler() - ahci_error_handler() - ahci_set_aggressive_devslp() - ahci_enable_fbs() - ahci_disable_fbs() - Comment 1: check if the HBA is idle - Comment 2: setting HBA to idle - Comment 3: wait for engine to stop. This could be as long as 500 msec --- ## AHCI driver 如何被執行? > 這裡紀錄的是我追code的思路, 之後可能會再整理成解說的角度 > [name=Nick Xiao] [time=AUG, 2020] [color=#907bf7] ### 找到關鍵字 "module": - 首先先找到管理實做的 structure - "ata_port_operations" </drivers/ata/licahci.c> ``` c=176 struct ata_port_operations ahci_ops = { .inherits = &sata_pmp_port_ops, .qc_defer = ahci_pmp_qc_defer, .qc_prep = ahci_qc_prep, .qc_issue = ahci_qc_issue, .qc_fill_rtf = ahci_qc_fill_rtf, .freeze = ahci_freeze, .thaw = ahci_thaw, .softreset = ahci_softreset, .hardreset = ahci_hardreset, .postreset = ahci_postreset, .pmp_softreset = ahci_softreset, .error_handler = ahci_error_handler, .post_internal_cmd = ahci_post_internal_cmd, .dev_config = ahci_dev_config, .scr_read = ahci_scr_read, .scr_write = ahci_scr_write, .pmp_attach = ahci_pmp_attach, .pmp_detach = ahci_pmp_detach, .set_lpm = ahci_set_lpm, .em_show = ahci_led_show, .em_store = ahci_led_store, .sw_activity_show = ahci_activity_show, .sw_activity_store = ahci_activity_store, #ifdef CONFIG_PM .port_suspend = ahci_port_suspend, .port_resume = ahci_port_resume, #endif .port_start = ahci_port_start, .port_stop = ahci_port_stop, }; EXPORT_SYMBOL_GPL(ahci_ops); ``` - 接著一路追被 linked 的地方 <drivers/ata/ahci_plaform.c> ``` c=80 static struct ata_port_operations ahci_platform_ops = { .inherits = &ahci_ops, .host_stop = ahci_host_stop, }; ``` ``` c=409 static struct platform_driver ahci_driver = { .probe = ahci_probe, .remove = ata_platform_remove_one, .driver = { .name = "ahci", .owner = THIS_MODULE, .of_match_table = ahci_of_match, .pm = &ahci_pm_ops, }, .id_table = ahci_devtype, }; module_platform_driver(ahci_driver); ``` - 到這後就會發現有個 module_platform_driver(ahci_driver), 這就是本次主要要探討的東西: > **Linux Module** ### Linux Module: - 在[這篇](https://stackoverflow.com/questions/22929065/difference-between-linux-loadable-and-built-in-modules)文章裡, 將 Linux Module 分成了 Built-in kernel modules 及 Loadable kernel module: - Built-in kernel modules: - 開機時自動載入 - Linux Kernel Image 大 - 需要重新 Compile linux kernel - 常用有 SCSI、SATA driver等 - Loadable kernel module: - 可動態載入 - Linux Kernel Image 小 - 不需要重新 Compile linux kernal - 常用有音效、NTFS driver等 :::info 爬文看到有些文章會分成 builtin module 和 external module, 區分的概念與上述的說明都是一樣的, 我認為記得這個概念即可 ::: 回到程式碼, 首先我們來看看 module_platform_driver 的定義和說明: </include/linux/platform_device.h> ```c=529 /* module_platform_driver() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() */ #define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) ``` 原先, 開發一個驅動程式會需要實做 module_init 以及 module_exit, 然而這裡提供的 module_platform_driver() 幫我們省去了這兩步驟, 連結部份如下: <include/linux/device.h> ```c=1108 #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit); ``` 再來看一下 platform_driver_register 及 platform_driver_unregister 這兩個 function pointrt 分別會做哪些事情: </drivers/base/platform.c> ```c=525 /** * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */ int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(platform_driver_register); ``` ```c=543 /** * platform_driver_unregister - unregister a driver for platform-level devices * @drv: platform driver structure */ void platform_driver_unregister(struct platform_driver *drv) { driver_unregister(&drv->driver); } EXPORT_SYMBOL_GPL(platform_driver_unregister); ``` 看來前面宣告的 static struct platform_driver ahci_driver 就是用在這個地方, ahci_driver 會在 module_init 時實做 ahci_probe() 和 ata_platform_remove_one() 然後進行註冊. :::info 這裡提醒一下, 因為 struct platform_driver ahci_driver 前面有加上 static, 所以在編譯階段就會初始化為 0 或是 NULL, 之後在 platform_driver_regiser() 時才能判斷正確 ::: - 從上面我們知道了 ahci_driver 這個 struct 被用在哪裡, 但好像只是知道如何使用 module, 卻還是不太清楚 module 的原理以及到底如何被呼叫的?繼續追吧! module_init() 及 module_exit() 的定義如下: ``` c=260 /** * module_init() - driver initialization entry point * @x: function to be run at kernel boot time or module insertion * * module_init() will either be called during do_initcalls() (if * builtin) or at module insertion time (if a module). There can only * be one per module. */ #define module_init(x) __initcall(x); /** * module_exit() - driver exit entry point * @x: function to be run when driver is removed * * module_exit() will wrap the driver clean-up code * with cleanup_module() when used with rmmod when * the driver is a module. If the driver is statically * compiled into the kernel, module_exit() has no effect. * There can only be one per module. */ #define module_exit(x) __exitcall(x); ``` 這邊除了說明兩個巨集的用途以外, 也提到了 do_initcalls(), 這個能在 <init/main.c> 中找到, 不過現在先來看一下 __initcall(x) 以及 __exitcall(x): <include/linux/init.h> ```c=214 #define __initcall(fn) device_initcall(fn) #define __exitcall(fn) \ static exitcall_t __exitcall_##fn __exit_call = fn ``` ```c=182 /* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */ #define early_initcall(fn) __define_initcall(fn, early) /* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. * Keep main.c:initcall_level_names[] in sync. */ #define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define core_initcall_sync(fn) __define_initcall(fn, 1s) #define postcore_initcall(fn) __define_initcall(fn, 2) #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) #define arch_initcall(fn) __define_initcall(fn, 3) #define arch_initcall_sync(fn) __define_initcall(fn, 3s) #define subsys_initcall(fn) __define_initcall(fn, 4) #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) #define fs_initcall(fn) __define_initcall(fn, 5) #define fs_initcall_sync(fn) __define_initcall(fn, 5s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define device_initcall_sync(fn) __define_initcall(fn, 6s) #define late_initcall(fn) __define_initcall(fn, 7) #define late_initcall_sync(fn) __define_initcall(fn, 7s) ``` 觀察 device_initcall 巨集時可以發現其他許多 XXX_initcall(fn) 並且都是對應到同個 __define_initcall(fn, 1), 這是一個管理的使用方式, 下面會拆解細說 :::info 這裡補充, 有關 #define 中使用 **'#'** 及 **'##'** 的差別, 前者用於字串型別的替換, 後者用於符號連接 ::: 我們以 module_platform_driver(ahci_driver) 一層層帶入 ```c=208 #define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) ``` > module_driver(ahci_driver, platform_driver_register, platform_driver_unregister) ```c=1122 #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ ``` > module_init(ahci_driver_init) ```c=286 #define module_init(x) __initcall(x); ``` > __initcall(ahci_driver_init) ```c=214 #define __initcall(fn) device_initcall(fn) ``` > device_initcall(ahci_driver_init) ```c=209 #define device_initcall(fn) __define_initcall(fn, 6) ``` > __define_initcall(ahci_driver_init, 6) ```c=178 /* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */ #define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn ``` > 最後這一個我們展開得到 ```c= static initcall_t __initcall_ahci_driver_init6 __used \ __attribute__((__section__(".initcall" "6" ".init"))) = ahci_driver_init ``` 這邊是指將 ahci_driver_init 加入 .initcall6.init 這個 section name 裡 :::warning 有關 $__attribute__$ 以及 $__section__$ 在網路上雖然有許多文章說明, 但我希望能找到更詳細的文件, 之後了解更細了再回過頭來補充及修改 ::: - 目前了解了使用方式以及 module 都做了哪些事情, 現在從 Linux kernel 的進入點 start_kernel 開始觀察與 module 相關的部份 </init/main.c> ``` C=471 asmlinkage void __init start_kernel(void) ``` - 流程大致如下: - start_kernel - rest_init - kernel_init - kernel_init_freeable - do_basic_setup - do_initcalls - do_initcall_level - do_one_initcall - do_one_initcall_debug - 程式碼 module_list: 管理每個載入 kernel 的 modle - 專有名詞 module stack: 使用已加載到kernel裡的module的funciton - Linux 指令 - ksyms: 搭配 '-a' 可以查看 kernel 或是目前載到 kernel 裡的 module 提供哪些 function 或 variable 可使用 (Ubuntu 18.04.4 為 command not found) - lsmod: 顯示目前存在 kernel 中的 module - modinfo: 可查看當前載入的 module ![](https://hackmd.io/_uploads/S1eIPQbMw.png) - insmod: 載入 module - rmmod: 移除 module ### 撰寫一個 dirver 小程式 > 程式內容為簡單的印字, 然後善用 Module 的機制, 觀察動態載入及移除時的相關行為 ## 王者歸來 OLLinux 驅動程式開發權威指南 > 5.2 Hello World 驅動程式 - 必須包含的兩個標頭檔 - #include <linux/module.h> - #include <linux/init.h> - MODULE_LICENSE 會影響到編譯時的警告 - 我們需要實做 hello_init() 和 hello_exit() 這兩個 function 並透過巨集 module_init 以及 module_exit 讓 kernel 呼叫 ### 參考資料: - [Kernel modules](https://linux-kernel-labs.github.io/refs/heads/master/labs/kernel_modules.html) - The Linux Kernel - [如何寫一個 Linux Kernel Module](https://jerrynest.io/how-to-write-a-linux-kernel-module/) - 傑瑞窩在這 - [linux驅動 之 module_init解析(上)](http://blog.csdn.net/richard_liujh/article/details/45669207) - 刘金辉 - [Linux內核很弔之module_init解析(下](http://blog.csdn.net/richard_liujh/article/details/46758073) - 刘金辉 - [[linux]將 driver 掛載到 kernel 上](https://pcnews.pixnet.net/blog/post/30747058) - pcman - [Linux AHCI驅動分析之設備硬件初始化](https://www.codenong.com/cs105966212/) - 码农家园 ###### tags: `Linux` `PCIe` `AHCI` `SATA` `Ubuntu`