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

- 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`