linux內核源碼分析 - nvme設備的初始化


本文為原創,轉載請注明:http://www.cnblogs.com/tolimit/

 

 

本文基於3.18.3內核的分析,nvme設備為pcie接口的ssd,其驅動名稱為nvme.ko,驅動代碼在drivers/block/nvme-core.c.

 

驅動的加載

  驅動加載實際就是module的加載,而module加載時會對整個module進行初始化,nvme驅動的module初始化函數為nvme_init(),如下:

static struct pci_driver nvme_driver = {
    .name        = "nvme",
    .id_table    = nvme_id_table,
    .probe        = nvme_probe,
    .remove        = nvme_remove,
    .shutdown    = nvme_shutdown,
    .driver        = {
        .pm    = &nvme_dev_pm_ops,
    },
    .err_handler    = &nvme_err_handler,
};

static int __init nvme_init(void)
{
    int result;

    /* 初始化等待隊列nvme_kthread_wait,此等待隊列用於創建nvme_kthread(只允許單進程創建nvme_kthread) */
    init_waitqueue_head(&nvme_kthread_wait);

    /* 創建一個workqueue叫nvme */
    nvme_workq = create_singlethread_workqueue("nvme");
    if (!nvme_workq)
        return -ENOMEM;

    /* 在內核中注冊新的一類塊設備驅動,名字叫nvme,注意這里只是注冊,表示kernel支持了nvme類的塊設備,返回一個major,之后所有的nvme設備的major都是此值 */
    result = register_blkdev(nvme_major, "nvme");
    if (result < 0)
        goto kill_workq;
    else if (result > 0)
        nvme_major = result;

    /* 注冊一些通知信息 */
    nvme_nb.notifier_call = &nvme_cpu_notify;
    result = register_hotcpu_notifier(&nvme_nb);
    if (result)
        goto unregister_blkdev;

    /* 注冊pci nvme驅動 */
    result = pci_register_driver(&nvme_driver);
    if (result)
        goto unregister_hotcpu;
    return 0;

 unregister_hotcpu:
    unregister_hotcpu_notifier(&nvme_nb);
 unregister_blkdev:
    unregister_blkdev(nvme_major, "nvme");
 kill_workq:
    destroy_workqueue(nvme_workq);
    return result;
}

  這里面其實最重要的就是做了兩件事,一件事是register_blkdev,注冊nvme這類塊設備,返回一個major,另一件事是注冊了nvme_driver,注冊了nvme_driver后,當有nvme設備插入后系統后,系統會自動調用nvme_driver->nvme_probe去初始化這個nvme設備.這時候可能會有疑問,系統是如何知道插入的設備是nvme設備的呢,注意看struct pci_driver nvme_driver這個結構體,里面有一個nvme_id_table,其內容如下:

/* Move to pci_ids.h later */
#define PCI_CLASS_STORAGE_EXPRESS    0x010802

static const struct pci_device_id nvme_id_table[] = {
    { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },
    { 0, }
};

再看看PCI_DEVICE_CLASS宏是如何定義的

#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \
     .class = (dev_class), .class_mask = (dev_class_mask), \
     .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \
     .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID

也就是當pci class為PCI_CLASS_STORAGE_EXPRESS時,就表示是nvme設備,並且這個是寫在設備里的,當設備插入host時,pci driver(並不是nvme driver)回去讀取這個值,然后判斷它需要哪個驅動去做處理.

 

nvme數據結構

  現在假設nvme.ko已經加載完了(注冊了nvme類塊設備,並且注冊了nvme driver),這時候如果有nvme盤插入pcie插槽,pci會自動識別到,並交給nvme driver去處理,而nvme driver就是調用nvme_probe去處理這個新加入的設備.

  在說nvme_probe之前,先說一下nvme設備的數據結構,首先,內核使用一個nvme_dev結構體來描述一個nvme設備, 一個nvme設備對應一個nvme_dev,nvme_dev如下:

/* nvme設備描述符,描述一個nvme設備 */
struct nvme_dev {
    struct list_head node;
    /* 設備的queue,一個nvme設備至少有2個queue,一個admin queue,一個io queue,實際情況一般都是一個admin queue,多個io queue,並且io queue會與CPU做綁定 */
    struct nvme_queue __rcu **queues;
    /* unsigned short的數組,每個CPU占一個,主要用於存放CPU上綁定的io queue的qid,一個CPU綁定一個queues,一個queues綁定到1到多個CPU上 */
    unsigned short __percpu *io_queue;
    /* ((void __iomem *)dev->bar) + 4096 */
    u32 __iomem *dbs;
    /* 此nvme設備對應的pci dev */
    struct pci_dev *pci_dev;
    /* dma池,主要是以4k為大小的dma塊,用於dma分配 */
    struct dma_pool *prp_page_pool;
    /* 也是dma池,但是不是以4k為大小的,是小於4k時使用 */
    struct dma_pool *prp_small_pool;
    /* 實例的id,第一個加入的nvme dev,它的instance為0,第二個加入的nvme,instance為1,也用於做/dev/nvme%d的顯示,%d實際就是instance的數值 */
    int instance;
    /* queue的數量, 等於admin queue + io queue */
    unsigned queue_count;
    /* 在線可以使用的queue數量,跟online cpu有關 */
    unsigned online_queues;
    /* 最大的queue id */
    unsigned max_qid;
    /* nvme queue支持的最大cmd數量,為((bar->cap) & 0xffff)或者1024的最小值 */
    int q_depth;
    /* 1 << (((bar->cap) >> 32) & 0xf),應該是每個io queue占用的bar空間 */
    u32 db_stride;
    /*    初始化設置的值
     *    dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM;
     *    dev->ctrl_config |= (PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
     *    dev->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;
     *    dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;
     */
    u32 ctrl_config;
    /* msix中斷所使用的entry,指針表示會使用多個msix中斷,使用的中斷的個數與io queue對等,多少個io queue就會申請多少個中斷
     * 並且讓每個io queue的中斷盡量分到不同的CPU上運行
     */
    struct msix_entry *entry;
    /* bar的映射地址,默認是映射8192,當io queue過多時,有可能會大於8192 */
    struct nvme_bar __iomem *bar;
    /* 其實就是塊設備,一張nvme卡有可能會有多個塊設備 */
    struct list_head namespaces;
    /* 對應的在/sys下的結構 */
    struct kref kref;
    /* 對應的字符設備,用於ioctl操作 */
    struct miscdevice miscdev;
    /* 2個work,暫時還不知道什么用 */
    work_func_t reset_workfn;
    struct work_struct reset_work;
    struct work_struct cpu_work;
    /* 這個nvme設備的名字,為nvme%d */
    char name[12];
    /* SN號 */
    char serial[20];
    char model[40];
    char firmware_rev[8];
    /* 這些值都是從nvme盤上獲取 */
    u32 max_hw_sectors;
    u32 stripe_size;
    u16 oncs;
    u16 abort_limit;
    u8 vwc;
    u8 initialized;
};

  在nvme_dev結構中,最最重要的數據就是nvme_queue,struct nvme_queue用來表示一個nvme的queue,每一個nvme_queue會申請自己的中斷,也有自己的中斷處理函數,也就是每個nvme_queue在驅動層面是完全獨立的.nvme_queue有兩種,一種是admin queue,一種是io queue,這兩種queue都用struct nvme_queue來描述,而這兩種queue的區別如下:

  • admin queue: 用於發送控制命令的queue,所有非io命令都會通過此queue發送給nvme設備,一個nvme設備只有一個admin queue,在nvme_dev中,使用queues[0]來描述.
  • io queue: 用於發送io命令的queue,所有io命令都是通過此queue發送給nvme設備,簡單來說讀/寫操作都是通過io queue發送給nvme設備的,一個nvme設備有一個或多個io queue,每個io queue的中斷會綁定到不同的一個或多個CPU上.在nvme_dev中,使用queues[1~N]來描述.

  以上說的io命令和非io命令都是nvme命令,比如快層下發一個寫request,nvme驅動就會根據此request構造出一個寫命令,將這個寫命令放入某個io queue中,當controller完成了這個寫命令后,會通過此io queue的中斷返回完成信息,驅動再將此完成信息返回給塊層.明白了兩種隊列的作用,我們看看具體的數據結構struct nvme_queue

/* nvme的命令隊列,其中包括sq和cq。一個nvme設備至少包含兩個命令隊列
 * 一個是控制命令隊列,一個是IO命令隊列
 */
struct nvme_queue {
    struct rcu_head r_head;
    struct device *q_dmadev;
    /* 所屬的nvme_dev */
    struct nvme_dev *dev;
    /* 中斷名字,名字格式為nvme%dq%d,在proc/interrupts可以查看到 */
    char irqname[24];    /* nvme4294967295-65535\0 */
    /* queue的鎖,當操作nvme_queue時,需要占用此鎖 */
    spinlock_t q_lock;
    /* sq的虛擬地址空間,主機需要發給設備的命令就存在這里面 */
    struct nvme_command *sq_cmds;
    /* cq的虛擬地址空間,設備返回的命令就存在這里面 */
    volatile struct nvme_completion *cqes;
    /* 實際就是sq_cmds的dma地址 */
    dma_addr_t sq_dma_addr;
    /* cq的dma地址,實際就是cqes對應的dma地址,用於dma傳輸 */
    dma_addr_t cq_dma_addr;
    /* 等待隊列,當sq滿時,進程會加到此等待隊列,等待有空閑的cmd區域 */
    wait_queue_head_t sq_full;
    /* wait queue的一個entry,主要是當cmdinfo滿時,會將它放入sq_full,而sq_full最后會通過它,喚醒nvme_thread */
    wait_queue_t sq_cong_wait;
    struct bio_list sq_cong;
    /* iod是讀寫請求的封裝,可以看成是一個bio的封裝,此鏈表有可能為空,比如admin queue就為空 */
    struct list_head iod_bio;
    /* 當前sq_tail位置,是nvme設備上的一個寄存器,告知設備最新的發送命令存在哪,存在於bar空間中 */
    u32 __iomem *q_db;
    /* cq和sq最大能夠存放的command數量 */
    u16 q_depth;
    /* 如果是admin queue,那么為0,之后的io queue按分配順序依次增加,主要用於獲取對應的irq entry,因為所有的queue的irq entry是一個數組 */
    u16 cq_vector;
    /* 當完成命令時會更新,當sq_head == sq_tail時表示cmd queue為空 */
    u16 sq_head;
    /* 當有新的命令存放到sq時,sq_tail++,如果sq_tail == q_depth,那么sq_tail會被重新設置為0,並且cq_phase翻轉 
     * 實際上就是一個環
     */
    u16 sq_tail;
    /* 驅動已經處理完成的cmd位置,當cq_head == sq_tail時,表示cmd隊列為空,當sq_tail == cq_head - 1時表示cmd隊列已滿 */
    u16 cq_head;
    /* 此nvme queue在此nvme設備中的queue id 
     * 0: 控制命令隊列
     */
    u16 qid;
    /* 初始設為1,主要用於判斷命令是否完成,當cqe.status & 1 != cq_phase時,表示命令還沒有完成
     * 當每次sq_tail == q_depth時,此值會取反
     */
    u8 cq_phase;
    u8 cqe_seen;
    /* 初始設為1 */
    u8 q_suspended;
    /* CPU親和性,用於設置此nvme queue能夠在哪些CPU上做中斷和中斷處理 */
    cpumask_var_t cpu_mask;
    struct async_cmd_info cmdinfo;
    /* 實際就是cmdinfo,此包含d_depth個cmdinfo,一個cmdid表示一個cmdinfo,當對應的bit為0時,表示此槽位空閑,為1時表示此槽位存有cmd 
     * 空閑的cmdinfo的默認完成回調函數都是special_completion
     * 其內存結構如下
     *                      d_depth bits                                       d_depth cmdinfo
     *   (每個bit一個cmdid,用於表示此cmdinfo是空閑還是被占用)              (d_depth個struct nvme_cmd_info)
     * |                                                      |                                                   |
     */
    unsigned long cmdid_data[];
};

 

  nvme_queue是nvme驅動最核心的數據結構,它是nvme驅動和nvme設備通信的橋梁,重點也要圍繞nvme_queue來說,之前也說過,一個nvme設備有多個nvme_queue(一個admin queue,至少一個io queue),每個nvme_queue是獨立的,它們有

  • 自己對應的中斷(irq)
  • 自己的submission queue(sq),用於將struct nvme command發送給nvme設備,並且最多能存dev->d_depth個nvme command
  • 自己的completion queue(cq),用於nvme設備將完成的命令信息(struct nvme_completion)發送給host,並且最多能存dev->d_depth個nvme_completion.
  • 自己的cmdinfo,用於描述一個nvme command.(struct nvme_cmd_info)

 

  可以把sq想象成一個struct nvme_command sq[dev->d_depth]的數組,而cq為struct nvme_completion cq[dev->d_depth]的數組.

  struct nvme_command主要用於存儲一個nvme命令,包括io命令,或者控制命令,當初始化好一個struct nvme_command后,直接將其下發給nvme設備,nvme設備就會根據它來執行對應操作,其結構如下:

struct nvme_command {
    union {
        struct nvme_common_command common;
        struct nvme_rw_command rw;
        struct nvme_identify identify;
        struct nvme_features features;
        struct nvme_create_cq create_cq;
        struct nvme_create_sq create_sq;
        struct nvme_delete_queue delete_queue;
        struct nvme_download_firmware dlfw;
        struct nvme_format_cmd format;
        struct nvme_dsm_cmd dsm;
        struct nvme_abort_cmd abort;
    };
};


struct nvme_format_cmd {
    __u8            opcode;
    __u8            flags;
    __u16            command_id;  
    __le32            nsid;
    __u64            rsvd2[4];
    __le32            cdw10;
    __u32            rsvd11[5];
};

  聯合體里面就是nvme支持的所有種類的命令,我隨便取了一個nvme_format_cmd,可以看看里面的變量,只要將這些變量設置正確,傳給nvme設備,nvme是能夠執行這個命令的.

  再看看struct nvme_completion,它用於描述完成的命令

struct nvme_completion {
    __le32    result;        /* Used by admin commands to return data */
    __u32    rsvd;
    __le16    sq_head;    /* how much of this queue may be reclaimed */
    __le16    sq_id;        /* submission queue that generated this entry */
    __u16    command_id;    /* of the command which completed */
    __le16    status;        /* did the command fail, and if so, why? */
};

  按之前說的,我們把sq和cq想象成兩個數組,比如驅動之前將一個nvme_format_cmd放到了sq[10]中,設備對這個nvme_format_cmd命令做了處理,這時候設備就會返回一個nvme_completion,並且把這個nvme_completion放入到cq[6](這里的index為6是假設,實際上我認為一個nvme_command對應一個nvme_completion,如果這個假設成立的話,正常情況這里應該也是為10),並且產生一個中斷,在nvme queue的中斷處理中,會獲取到這個nvme_completion,並通過nvme_completion->sq_head就能夠獲取到sq[10]中的nvme_format_cmd.這樣sq和cq就能夠完全聯系起來了.

  對於驅動來說,一個命令應該是由兩部分組成:

  1. 命令的格式,要通過怎樣的格式發送給硬件,硬件能夠識別.
  2. 命令的額外信息.

  對於第一點,實際上就是nvme_command來做,而對於第二點,就需要用nvme_cmd_info來保存了,nvme_cmd_info也是一個數組,根據d_depth來分配長度(因為sq和cq都是根據d_depth來分配長度),並且nvme_queue還會維護一個nvme_cmd_info的used_bitmap,用來表示哪個nvme_cmd_info數組中哪個cmd_info已經被占用,nvme_cmd_info如下:

struct nvme_cmd_info {
    nvme_completion_fn fn;    // 命令完成后的回調函數
    void *ctx;                // 命令的信息,不同命令使用不同結構來描述,所以這里只提供一個指針
    unsigned long timeout;    // 命令允許的超時時間
    int aborted;            // 命令是否作廢
};

  現在來說說nvme驅動怎么把nvme_command,nvme_completion和nvme_cmd_info聯系起來,以上面的nvme_format_cmd為例,假設nvme驅動要發送一個nvme_format_cmd命令,那么先會從nvme_cmd_info的used_bitmap中獲取一個空閑的nvme_cmd_info(包括這個cmd_info對應的index,實際就是nvme_cmd_info的數組下標,也稱為cmdid),然后根據nvme_format_cmd驅動需要做的事情和信息,來初始化這個nvme_cmd_info,將nvme_format_cmd中的command_id設置為cmdid,發送nvme_format_cmd給nvme設備,nvme設備處理完畢后,發送nvme_format_cmd對應的nvme_completion給host,host獲取到此nvme_comletion,從command_id中獲取到cmdid,根據獲取到的cmdid就能夠獲取到對應的nvme_cmd_info了.也就是說,在將命令發送給nvme設備時,要將cmd_info對應的cmd_id也一並傳下去,之后命令返回時,nvme設備也會將這個cmd_id傳回來,這樣就能夠將三者對應聯系起來了.

 

nvme設備初始化

  之前也說了,nvme驅動加載好后,如果有新的nvme設備加入,那么會通過nvme_probe來初始化這個nvme設備,我們先看看nvme_probe這個函數.

/* 當插入一個nvme設備時,會通過此函數進行nvme設備的初始化 */
static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int result = -ENOMEM;
    /* nvme設備描述符 */
    struct nvme_dev *dev;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;
    /* nvme用的是msi/msix中斷,這里應該是是按numa內的CPU個數來分配entry數量,entry是msix_entry */
    dev->entry = kcalloc(num_possible_cpus(), sizeof(*dev->entry),
                                GFP_KERNEL);
    if (!dev->entry)
        goto free;
    /* struct nvme_queue,數量是numa內的CPU個數+1 */
    dev->queues = kcalloc(num_possible_cpus() + 1, sizeof(void *),
                                GFP_KERNEL);
    if (!dev->queues)
        goto free;
    /* unsigned short的數組,每個CPU占一個 */
    dev->io_queue = alloc_percpu(unsigned short);
    if (!dev->io_queue)
        goto free;

    /* 初始化namespace鏈表 */
    INIT_LIST_HEAD(&dev->namespaces);
    /* reset work的調用函數 */
    dev->reset_workfn = nvme_reset_failed_dev;
    INIT_WORK(&dev->reset_work, nvme_reset_workfn);
    INIT_WORK(&dev->cpu_work, nvme_cpu_workfn);
    dev->pci_dev = pdev;
    pci_set_drvdata(pdev, dev);
    /* 分配一個ID,保存到dev->instance里,實際上第一個加入的nvme設備,它的instance為0,第二個加入的nvme設備,instance為1,以此類推 */
    result = nvme_set_instance(dev);
    if (result)
        goto free;

    /* 主要創建兩個dma pool,一個是4k大小(prp list page),一個是256B大小(prp list 256) */
    result = nvme_setup_prp_pools(dev);
    if (result)
        goto release;

    kref_init(&dev->kref);
    /* 1.做bar空間的映射,映射地址存放到nvme_dev->bar 
      * 2.當此設備是系統中第一個加載的nvme設備或者nvme_thread沒有啟動時,就會啟動一個nvme_thread
     * 3.初始化nvme的io queue(主要)
     */
    result = nvme_dev_start(dev);
    if (result) {
        if (result == -EBUSY)
            goto create_cdev;
        goto release_pools;
    }

    /* 分配request queue和disk,執行完此函數后,在/dev/下就有此nvme設備了 */
    result = nvme_dev_add(dev);
    if (result)
        goto shutdown;

 create_cdev:
     /* 這里開始分配一個對應的混雜設備,可以理解為字符設備,主要用於應用層用ioctl接口來操作此nvme設備 
      * 這個字符設備的名字為nvme%d
      */
    scnprintf(dev->name, sizeof(dev->name), "nvme%d", dev->instance);
    dev->miscdev.minor = MISC_DYNAMIC_MINOR;
    dev->miscdev.parent = &pdev->dev;
    dev->miscdev.name = dev->name;
    dev->miscdev.fops = &nvme_dev_fops;
    result = misc_register(&dev->miscdev);
    if (result)
        goto remove;

    dev->initialized = 1;
    return 0;

 remove:
    nvme_dev_remove(dev);
    nvme_free_namespaces(dev);
 shutdown:
    nvme_dev_shutdown(dev);
 release_pools:
    nvme_free_queues(dev, 0);
    nvme_release_prp_pools(dev);
 release:
    nvme_release_instance(dev);
 free:
    free_percpu(dev->io_queue);
    kfree(dev->queues);
    kfree(dev->entry);
    kfree(dev);
    return result;
}

  nvme_probe函數主要做如下幾件事情:

  1. 為中斷創建msi/msix的entry,按CPU的數量進行entry的分配,為什么要按照CPU數量進行分配,因為每個io queue會占用一個.而整個系統io queue最大值也就是possible_cpus.
  2. 分配possible個cpus+1的queue結構體,possible應該是系統最大能夠插入的cpu核個數,其不等於online_cpus,注意這里是possible_cpus+1,而中斷的msi/msix的entry個數為possible_cpus,而每個queue會用一個entry,這樣不是就會導致有一個queue是沒有entry用的嗎?實際上admin queue和第一個io queue會共用entry0.
  3. 分配instance,實際上就是一個nvme id,從0開始依次遞增.
  4. 分配兩個dma pool,一個pool中的元素大小為4k,一個是256B,這兩個pool都是用於數據傳輸時做dma分配用的.
  5. 調用nvme_dev_start和nvme_dev_add,這兩個是主要函數,之后重點看這兩個函數.

  nvme_dev_start和nvme_dev_add是負責不同的初始化,簡單點說,nvme_dev_start是將硬件和驅動的聯系進行初始化,當nvme_dev_start執行完成后,此nvme設備實際已經能夠通過驅動正常使用了,但實際操作系統還是無法使用此設備,原因是需要nvme_dev_add函數將此設備注冊到操作系統中,實際就是注冊對應的gendisk和request queue,這樣在/dev/和操作系統中都能過對此nvme設備進行操作.

 

nvme_dev_start

  nvme_dev_start函數主要是做硬件方面與驅動方面的傳輸通道的初始化和硬件的一些初始化,實際主要就是建立admin queue和io queue,並且為這些queue綁定到各自的irq上.

/* 1.做bar空間的映射,映射地址存放到nvme_dev->bar 
 * 2.當此設備是系統中第一個加載的nvme設備或者nvme_thread沒有啟動時,就會啟動一個nvme_thread
 * 3.初始化nvme的io queue
 */
static int nvme_dev_start(struct nvme_dev *dev)
{
    int result;
    bool start_thread = false;

    /* 主要做bar空間的映射,映射地址存放到nvme_dev->bar,並且從bar空間獲取nvme設備的d_queue,d_queue是queue中允許的最大cmd數量 */
    result = nvme_dev_map(dev);
    if (result)
        return result;

    /* 初始化控制命令隊列,中斷處理函數為nvme_irq */
    result = nvme_configure_admin_queue(dev);
    if (result)
        goto unmap;

    spin_lock(&dev_list_lock);
    /* 當此設備是系統中第一個加載的nvme設備或者nvme_thread沒有啟動時,就會啟動一個nvme_thread */
    if (list_empty(&dev_list) && IS_ERR_OR_NULL(nvme_thread)) {
        start_thread = true;
        nvme_thread = NULL;
    }
    list_add(&dev->node, &dev_list);
    spin_unlock(&dev_list_lock);

    if (start_thread) {
        /* 在此nvme設備的加載上下文中創建nvme_thread */
        nvme_thread = kthread_run(nvme_kthread, NULL, "nvme");
        wake_up(&nvme_kthread_wait);
    } else
        /* 非創建nvme_thread的nvme設備就會在這里等待nvme_thread創建完成 */
        wait_event_killable(nvme_kthread_wait, nvme_thread);

    if (IS_ERR_OR_NULL(nvme_thread)) {
        result = nvme_thread ? PTR_ERR(nvme_thread) : -EINTR;
        goto disable;
    }

    /* 初始化nvme的io queue,此為nvme_queue,一個nvme設備至少一個admin queue,一個io queue */
    result = nvme_setup_io_queues(dev);
    if (result && result != -EBUSY)
        goto disable;

    return result;

 disable:
    nvme_disable_queue(dev, 0);
    nvme_dev_list_remove(dev);
 unmap:
    nvme_dev_unmap(dev);
    return result;
}

  需要注意,d_queue默認是1024,驅動會通過此nvme設備的pci bar空間獲取到設備支持的d_queue,並取兩者的最小值作為此設備所有queue的d_queue,d_queue是queue中允許存放的cmd數量最大值.

  d_queue獲取到后,第一件事情是初始化admin queue,使用nvme_configure_admin_queue:

/* 初始化控制命令隊列,中斷處理函數為nvme_irq */
static int nvme_configure_admin_queue(struct nvme_dev *dev)
{
    int result;
    u32 aqa;
    u64 cap = readq(&dev->bar->cap);
    struct nvme_queue *nvmeq;

    /* 應該是告訴nvme設備禁止操作 
     * 實現方法是對bar空間的NVME_CC_ENABLEbit做操作,因為當前還沒有做irq分配和使用,只能通過寄存器的方法做設置
     */
    result = nvme_disable_ctrl(dev, cap);
    if (result < 0)
        return result;

    /* 獲取qid為0的nvme queue,實際上就是admin queue */
    nvmeq = raw_nvmeq(dev, 0);
    /* 如果不存在,則分配一個nvme queue的內存空間用於admin queue(qid 0) */
    /* 主要分配cq和sq的dma空間,大小為depth*(struct nvme_completion),depth*(struct nvme_command) 
     * 注意sq和cq的dma空間都必須使用dma_alloc_coherent來分配
     */
    if (!nvmeq) {
        nvmeq = nvme_alloc_queue(dev, 0, 64, 0);
        if (!nvmeq)
            return -ENOMEM;
    }

    aqa = nvmeq->q_depth - 1;
    aqa |= aqa << 16;

    dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM;
    dev->ctrl_config |= (PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
    dev->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;
    dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;

    /* 初始化sq和cq */
    writel(aqa, &dev->bar->aqa);
    writeq(nvmeq->sq_dma_addr, &dev->bar->asq);
    writeq(nvmeq->cq_dma_addr, &dev->bar->acq);
    writel(dev->ctrl_config, &dev->bar->cc);

    /* 應該是告訴nvme設備使能操作 */
    result = nvme_enable_ctrl(dev, cap);
    if (result)
        return result;

    /* 分配中斷,這里主要分配cq的中斷,中斷處理函數為nvme_irq */
    result = queue_request_irq(dev, nvmeq, nvmeq->irqname);
    if (result)
        return result;

    spin_lock_irq(&nvmeq->q_lock);
    /* 初始化cq和sq */
    nvme_init_queue(nvmeq, 0);
    spin_unlock_irq(&nvmeq->q_lock);
    return result;
}


/* 分配cq和sq的dma空間,大小為depth*(struct nvme_completion),depth*(struct nvme_command) */
static struct nvme_queue *nvme_alloc_queue(struct nvme_dev *dev, int qid,
                            int depth, int vector)
{
    struct device *dmadev = &dev->pci_dev->dev;
    unsigned extra = nvme_queue_extra(depth);
    struct nvme_queue *nvmeq = kzalloc(sizeof(*nvmeq) + extra, GFP_KERNEL);
    if (!nvmeq)
        return NULL;

    /* cq的dma區域,存放completion cmd的地方 */
    nvmeq->cqes = dma_alloc_coherent(dmadev, CQ_SIZE(depth),
                    &nvmeq->cq_dma_addr, GFP_KERNEL);
    if (!nvmeq->cqes)
        goto free_nvmeq;
    memset((void *)nvmeq->cqes, 0, CQ_SIZE(depth));

    /* sq的dma區域,存放submission cmd的地方 */
    nvmeq->sq_cmds = dma_alloc_coherent(dmadev, SQ_SIZE(depth),
                    &nvmeq->sq_dma_addr, GFP_KERNEL);
    if (!nvmeq->sq_cmds)
        goto free_cqdma;

    if (qid && !zalloc_cpumask_var(&nvmeq->cpu_mask, GFP_KERNEL))
        goto free_sqdma;

    nvmeq->q_dmadev = dmadev;
    nvmeq->dev = dev;
    snprintf(nvmeq->irqname, sizeof(nvmeq->irqname), "nvme%dq%d",
            dev->instance, qid);
    spin_lock_init(&nvmeq->q_lock);
    nvmeq->cq_head = 0;
    nvmeq->cq_phase = 1;
    /* 當sq中的cmdinfo滿時,會將進程加入到此waitqueue做等待 */
    init_waitqueue_head(&nvmeq->sq_full);
    /* sq_cong_wait是用於加入到sq_full,當sq_full喚醒sq_cong_wait時,實際上是喚醒了nvme_thread */
    init_waitqueue_entry(&nvmeq->sq_cong_wait, nvme_thread);
    bio_list_init(&nvmeq->sq_cong);
    INIT_LIST_HEAD(&nvmeq->iod_bio);
    /* 當前sq_tail位置,是nvme設備上的一個寄存器,存在於bar空間中 
     * 發送命令流程: cmd放入sq_cmds,sq_head++,更新sq_head到此q_db,nvme設置會感知到,然后dma sq cmds,並處理sq cmd.
     */
    nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];
    /* 1024或者nvme設備支持的最大值 */
    nvmeq->q_depth = depth;
    /* admin queue為0,io queue從0~io queue count */
    nvmeq->cq_vector = vector;
    /* queue id, admin queue為0, io queue為1~ io_queue_count+1 */
    nvmeq->qid = qid;
    nvmeq->q_suspended = 1;
    /* nvme設備的queue_count++ */
    dev->queue_count++;
    rcu_assign_pointer(dev->queues[qid], nvmeq);

    return nvmeq;

 free_sqdma:
    dma_free_coherent(dmadev, SQ_SIZE(depth), (void *)nvmeq->sq_cmds,
                            nvmeq->sq_dma_addr);
 free_cqdma:
    dma_free_coherent(dmadev, CQ_SIZE(depth), (void *)nvmeq->cqes,
                            nvmeq->cq_dma_addr);
 free_nvmeq:
    kfree(nvmeq);
    return NULL;
}



/* 初始化cq和sq */
static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid)
{
    struct nvme_dev *dev = nvmeq->dev;
    /* 大部分情況都是0 */
    unsigned extra = nvme_queue_extra(nvmeq->q_depth);

    nvmeq->sq_tail = 0;
    nvmeq->cq_head = 0;
    nvmeq->cq_phase = 1;
    nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];
    memset(nvmeq->cmdid_data, 0, extra);
    memset((void *)nvmeq->cqes, 0, CQ_SIZE(nvmeq->q_depth));
    /* 告訴設備取消處理當前設備中的io請求 */
    nvme_cancel_ios(nvmeq, false);
    nvmeq->q_suspended = 0;
    dev->online_queues++;
}

 

  到這里admin queue已經初始化完成,可以通過對admin queue發送nvme控制命令來操作nvme設置.admin queue初始化完成后的結果如下:

  • qid為0就是admin queue,並且nvme_dev->queues[0]就是admin queue.
  • nvme_dev->entrys[0]是admin queue使用的.

  admin queue初始化完成后,創建nvme_thread,此內核線程不會在初始化流程中使用,暫時先不看,接下來就是初始化io queue了.

  初始化io queue是nvme_setup_io_queue函數

/* 初始化nvme設備的所有io queue */
static int nvme_setup_io_queues(struct nvme_dev *dev)
{
    struct nvme_queue *adminq = raw_nvmeq(dev, 0);
    struct pci_dev *pdev = dev->pci_dev;
    int result, i, vecs, nr_io_queues, size;

    /* 以CPU個數來分配io queue */
    nr_io_queues = num_possible_cpus();
    /* 此函數用於設置controller支持的io queue數量(通過發送NVME_FEAT_NUM_QUEUES命令),nvme driver最優的結果是cpus個數個io queue
     * 在服務器上nvme設備肯定不會支持那么多io queue,所以設置時controller最多只會設置自己支持的io queue,並返回自己支持的io queue個數
     * 最后我們選擇最小的那個數作為io queue個數,因為也有可能CPU很少,controller支持的io queue很多
     */
    result = set_queue_count(dev, nr_io_queues);
    if (result < 0)
        return result;
    if (result < nr_io_queues)
        nr_io_queues = result;

    /* 4096 + ((nr_io_queues + 1) * 8 * dev->db_stride) */
    size = db_bar_size(dev, nr_io_queues);
    /* size過大,重新映射bar空間 */
    if (size > 8192) {
        iounmap(dev->bar);
        do {
            dev->bar = ioremap(pci_resource_start(pdev, 0), size);
            if (dev->bar)
                break;
            if (!--nr_io_queues)
                return -ENOMEM;
            size = db_bar_size(dev, nr_io_queues);
        } while (1);
        dev->dbs = ((void __iomem *)dev->bar) + 4096;
        adminq->q_db = dev->dbs;
    }

    /* Deregister the admin queue's interrupt */
    /* 釋放admin queue的irq */
    free_irq(dev->entry[0].vector, adminq);

    for (i = 0; i < nr_io_queues; i++)
        dev->entry[i].entry = i;
    /* 每個io queue申請一個msix,如果不支持msix,則用msi */
    vecs = pci_enable_msix_range(pdev, dev->entry, 1, nr_io_queues);
    if (vecs < 0) {
        vecs = pci_enable_msi_range(pdev, 1, min(nr_io_queues, 32));
        if (vecs < 0) {
            vecs = 1;
        } else {
            for (i = 0; i < vecs; i++)
                dev->entry[i].vector = i + pdev->irq;
        }
    }

    /*
     * Should investigate if there's a performance win from allocating
     * more queues than interrupt vectors; it might allow the submission
     * path to scale better, even if the receive path is limited by the
     * number of interrupts.
     */
    nr_io_queues = vecs;
    dev->max_qid = nr_io_queues;

    /* 重新分配admin queue的irq */
    result = queue_request_irq(dev, adminq, adminq->irqname);
    if (result) {
        adminq->q_suspended = 1;
        goto free_queues;
    }

    /* Free previously allocated queues that are no longer usable */
    /* 釋放多余的io queue */
    nvme_free_queues(dev, nr_io_queues + 1);
    /* 分配io queue需要的內存,並且分配對應的irq,最后設置CPU親和性 */
    nvme_assign_io_queues(dev);

    return 0;

 free_queues:
    nvme_free_queues(dev, 1);
    return result;
}



/* 分配一個nvme queue,包括其需要的CQ和SQ空間和DMA地址 */
/* 通過admin queue告知nvme設備創建cq和sq,並且分配對應的irq */
static void nvme_create_io_queues(struct nvme_dev *dev)
{
    unsigned i, max;

    max = min(dev->max_qid, num_online_cpus());
    for (i = dev->queue_count; i <= max; i++)
        /* 分配一個nvme queue,包括其需要的CQ和SQ空間和DMA地址,注意這里第一個io queue使用的entry是0,也就是和admin queue共用 */
        if (!nvme_alloc_queue(dev, i, dev->q_depth, i - 1))
            break;

    max = min(dev->queue_count - 1, num_online_cpus());
    for (i = dev->online_queues; i <= max; i++)
        /* 通過admin queue告知nvme設備創建cq和sq,並且分配對應的irq */
        if (nvme_create_queue(raw_nvmeq(dev, i), i))
            break;
}


static int nvme_create_queue(struct nvme_queue *nvmeq, int qid)
{
    struct nvme_dev *dev = nvmeq->dev;
    int result;

    /* 通過admin queue將nvme_admin_create_cq命令發送給nvme設備,主要將當前queue的cq_dma地址和qid傳給nvme設備,這樣就能將cq關聯起來 */
    result = adapter_alloc_cq(dev, qid, nvmeq);
    if (result < 0)
        return result;

    /* 通過admin queue將nvme_admin_create_sq命令發送給nvme設備,主要將當前queue的sq_dma地址和qid傳給nvme設備,這樣就能將sq關聯起來 */
    result = adapter_alloc_sq(dev, qid, nvmeq);
    if (result < 0)
        goto release_cq;

    /* 為此queue創建一個irq */
    result = queue_request_irq(dev, nvmeq, nvmeq->irqname);
    if (result < 0)
        goto release_sq;

    spin_lock_irq(&nvmeq->q_lock);
    nvme_init_queue(nvmeq, qid);
    spin_unlock_irq(&nvmeq->q_lock);

    return result;

 release_sq:
    adapter_delete_sq(dev, qid);
 release_cq:
    adapter_delete_cq(dev, qid);
    return result;
}



/* 分配io queue需要的內存,並且分配對應的irq,最后設置CPU親和性 */
static void nvme_assign_io_queues(struct nvme_dev *dev)
{
    unsigned cpu, cpus_per_queue, queues, remainder, i;
    cpumask_var_t unassigned_cpus;

    /* 分配一個nvme queue,包括其需要的CQ和SQ空間和DMA地址 */
    /* 告知nvme設備創建cq和sq,並且分配對應的irq */
    nvme_create_io_queues(dev);

    /* 獲取queue的數量,其至少<=CPUS */
    queues = min(dev->online_queues - 1, num_online_cpus());
    if (!queues)
        return;

    /* 計算每個io queue的中斷可以綁定到多少個CPU上,結果>=1 */
    cpus_per_queue = num_online_cpus() / queues;
    /* 剩余的CPU個數,因為queues <= cpus,當queues < cpus時,那么必然有一些queues綁定的cpus比其他的少一個,具體看下面的代碼 */
    remainder = queues - (num_online_cpus() - queues * cpus_per_queue);
    if (!alloc_cpumask_var(&unassigned_cpus, GFP_KERNEL))
        return;

    /* 將所有可用的CPU的mask復制到unassigned_cpus */
    cpumask_copy(unassigned_cpus, cpu_online_mask);
    /* 獲取第一個可用的CPU */
    cpu = cpumask_first(unassigned_cpus);
    /* 遍歷所有的io queue,從1開始是因為0是admin queue */
    for (i = 1; i <= queues; i++) {
        /* 根據獲取對應的io queue */
        struct nvme_queue *nvmeq = lock_nvmeq(dev, i);
        cpumask_t mask;

        /* 清除此io queue的cpumask */
        cpumask_clear(nvmeq->cpu_mask);
        /* 如果unassigned_cpus為0,表示沒有CPU可以使用,則退出,之后會初始化nvme dev失敗 */
        if (!cpumask_weight(unassigned_cpus)) {
            unlock_nvmeq(nvmeq);
            break;
        }

        /* 根據CPU ID.獲取此CPU的cpumask */
        mask = *get_cpu_mask(cpu);
        /* 設置此io queue使用此CPU */
        nvme_set_queue_cpus(&mask, nvmeq, cpus_per_queue);
        /* 如果綁定的CPU個數少於cpus_per_queue,那么先綁定此CPU對應的超線程的其他CPU */
        if (cpus_weight(mask) < cpus_per_queue)
            nvme_add_cpus(&mask, unassigned_cpus,
                topology_thread_cpumask(cpu),
                nvmeq, cpus_per_queue);

        /* 如果綁定的CPU個數還少於cpus_per_queue,那么綁定此CPU對應的同一個socket上其他CPU */
        if (cpus_weight(mask) < cpus_per_queue)
            nvme_add_cpus(&mask, unassigned_cpus,
                topology_core_cpumask(cpu),
                nvmeq, cpus_per_queue);

        /* 如果綁定的CPU個數還少於cpus_per_queue,那么綁定此CPU對應的node上的所有CPU */
        if (cpus_weight(mask) < cpus_per_queue)
            nvme_add_cpus(&mask, unassigned_cpus,
                cpumask_of_node(cpu_to_node(cpu)),
                nvmeq, cpus_per_queue);

        /* 如果綁定的CPU個數還少於cpus_per_queue,那么綁定此CPU對應的node最近的node上的所有CPU */
        if (cpus_weight(mask) < cpus_per_queue)
            nvme_add_cpus(&mask, unassigned_cpus,
                cpumask_of_node(
                    nvme_find_closest_node(
                        cpu_to_node(cpu))),
                nvmeq, cpus_per_queue);

        /* 如果綁定的CPU個數還少於cpus_per_queue,那么綁定所有可用的CPU */
        if (cpus_weight(mask) < cpus_per_queue)
            nvme_add_cpus(&mask, unassigned_cpus,
                unassigned_cpus,
                nvmeq, cpus_per_queue);

        WARN(cpumask_weight(nvmeq->cpu_mask) != cpus_per_queue,
            "nvme%d qid:%d mis-matched queue-to-cpu assignment\n",
            dev->instance, i);

        /* 到這里,已經獲取到了此queue對應綁定的CPU的cpumask,並且哪個CPU綁定哪個queue,已經寫到nvme_dev->io_queue */
        
        /* 根據cpumask,設置中斷的親和性 */
        irq_set_affinity_hint(dev->entry[nvmeq->cq_vector].vector,
                            nvmeq->cpu_mask);
        /* 將這些綁定的CPU從unassigned_cpus中刪除 */
        cpumask_andnot(unassigned_cpus, unassigned_cpus,
                        nvmeq->cpu_mask);
        /* cpu += 1 */
        cpu = cpumask_next(cpu, unassigned_cpus);
        /* 如果此時remainder為0了,那么從下一個queue開始,它綁定的cpus+1,也就是下一個及其之后的queue,綁定的CPUS都比之前的多1 */
        if (remainder && !--remainder)
            cpus_per_queue++;
        unlock_nvmeq(nvmeq);
    }
    WARN(cpumask_weight(unassigned_cpus), "nvme%d unassigned online cpus\n",
                                dev->instance);
    i = 0;
    cpumask_andnot(unassigned_cpus, cpu_possible_mask, cpu_online_mask);
    /* 如果還有剩余的可用CPU的情況,那么就從queue1開始依次綁到剩余這些CPU上 */
    for_each_cpu(cpu, unassigned_cpus)
        *per_cpu_ptr(dev->io_queue, cpu) = (i++ % queues) + 1;
    free_cpumask_var(unassigned_cpus);
}

  一個nvme設備會有多個io queue,每個io queue會有自己的中斷,並且nvme設備會將每個io queue的中斷綁定到不同的CPU上(實際上並不是真正的做了綁定,注意irq_set_affinity_hint這個函數,它實際上是告知使用者,這個irq更適合在哪些CPU上做處理,但是kernel還是有可能將這個IRQ放到不屬於這些CPU中的CPU去處理,不過如果在用戶層使用了irqbalance命令,那么irqbalance會將這個IRQ放到這個函數設置的CPU上去處理.具體可以看/proc/irq中的值就能明白了,它改變的是smp_affinity_hint值,而非smp_affinity),就有了上面的代碼.一般情況應該是一個io queue綁定到多個CPU上,比如CPU有16個,io queue有8個,那么io queue[0]綁定到cpu0,1上,io queue[1]綁定到cpu2,3上,依次類推.當io queue初始化完成后,一些需要注意的細節如下:

  • io queue使用的entry是從0開始的,也就是io queue0會與admin queue共用一個entry.
  • nvme_dev->queues是從1開始保存io queue.
  • queue的sq_dma,cq_dma和qid通過admin queue發送給nvme設備,nvme設備會將其做綁定.並且注意,在nvme_alloc_queue時,queue->q_db指向的位置是通過qid計算的,所以實際上,sq_dma,cq_dma,qid和q_db都能過聯系起來了.

  到這里,admin queue和io queue都初始化完成了,之后就是在塊層注冊設備的操作.

 

nvme_add_dev

static int nvme_dev_add(struct nvme_dev *dev)
{
    struct pci_dev *pdev = dev->pci_dev;
    int res;
    unsigned nn, i;
    struct nvme_ns *ns;
    struct nvme_id_ctrl *ctrl;
    struct nvme_id_ns *id_ns;
    void *mem;
    dma_addr_t dma_addr;
    int shift = NVME_CAP_MPSMIN(readq(&dev->bar->cap)) + 12;

    /* 分配一個一致性dma區域,注意大小是8192B,前4096B放盤的信息,后面4096B空閑,之后會使用 */
    mem = dma_alloc_coherent(&pdev->dev, 8192, &dma_addr, GFP_KERNEL);
    if (!mem)
        return -ENOMEM;

    /* 向controller發送一個identify命令,此命令會讓controller將nvme卡的信息保存到mem這塊一致性dma區域中 */
    res = nvme_identify(dev, 0, 1, dma_addr);
    if (res) {
        dev_err(&pdev->dev, "Identify Controller failed (%d)\n", res);
        res = -EIO;
        goto out;
    }

    /* 已經獲取到信息,包括sn號,model,fw版本,用戶可用容量等信息,注意,nn是表示此nvme物理盤生成多少個塊設備 */
    ctrl = mem;
    /* 決定了生成多少個塊設備 */
    nn = le32_to_cpup(&ctrl->nn);
    dev->oncs = le16_to_cpup(&ctrl->oncs);
    dev->abort_limit = ctrl->acl + 1;
    dev->vwc = ctrl->vwc;
    memcpy(dev->serial, ctrl->sn, sizeof(ctrl->sn));
    memcpy(dev->model, ctrl->mn, sizeof(ctrl->mn));
    memcpy(dev->firmware_rev, ctrl->fr, sizeof(ctrl->fr));
    if (ctrl->mdts)
        dev->max_hw_sectors = 1 << (ctrl->mdts + shift - 9);
    if ((pdev->vendor == PCI_VENDOR_ID_INTEL) &&
            (pdev->device == 0x0953) && ctrl->vs[3])
        dev->stripe_size = 1 << (ctrl->vs[3] + shift);

    id_ns = mem;
    for (i = 1; i <= nn; i++) {
        res = nvme_identify(dev, i, 0, dma_addr);
        if (res)
            continue;

        if (id_ns->ncap == 0)
            continue;

        /* 通過admin queue獲取設備盤容量,lba大小等信息,存放到mem的后4096B中 */
        res = nvme_get_features(dev, NVME_FEAT_LBA_RANGE, i,
                            dma_addr + 4096, NULL);
        if (res)
            memset(mem + 4096, 0, 4096);

        /* 分配disk和request queue,一個塊設備就是一個namespace */
        ns = nvme_alloc_ns(dev, i, mem, mem + 4096);
        /* 加入到nvme_dev->namespace鏈表中 */
        if (ns)
            list_add_tail(&ns->list, &dev->namespaces);
    }
    /* 將disk添加到系統中,這樣用戶就能在/dev/下面看到了 */
    list_for_each_entry(ns, &dev->namespaces, list)
        add_disk(ns->disk);
    res = 0;

 out:
    dma_free_coherent(&dev->pci_dev->dev, 8192, mem, dma_addr);
    return res;
}

  此函數主要做幾件事情:

  1. 獲取nvme設備的信息.
  2. 根據nvme設備的信息,創建對應的namespace,一個namespace實際就是一個塊設備
  3. 將創建的namespace加入到系統中的塊設備中.

  主要是通過nvme_alloc_ns函數來初始化一個namespace,一個namespace是一個塊設備,一個塊設備主要初始化兩個結構,一個是gendisk,一個是request queue,當兩個結構都初始化好后,調用add_disk()函數,這個塊設備就會正式加入到系統中的塊設備中.

static struct nvme_ns *nvme_alloc_ns(struct nvme_dev *dev, unsigned nsid,
            struct nvme_id_ns *id, struct nvme_lba_range_type *rt)
{
    struct nvme_ns *ns;
    struct gendisk *disk;
    int lbaf;

    if (rt->attributes & NVME_LBART_ATTRIB_HIDE)
        return NULL;

    ns = kzalloc(sizeof(*ns), GFP_KERNEL);
    if (!ns)
        return NULL;
    /* 分配一個request queue */
    ns->queue = blk_alloc_queue(GFP_KERNEL);
    if (!ns->queue)
        goto out_free_ns;
    ns->queue->queue_flags = QUEUE_FLAG_DEFAULT;
    /* 禁止合並操作,包括bio合並到request操作,兩個request合並操作 */
    queue_flag_set_unlocked(QUEUE_FLAG_NOMERGES, ns->queue);
    /* 表示是一個ssd設備 */
    queue_flag_set_unlocked(QUEUE_FLAG_NONROT, ns->queue);
    queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, ns->queue);
    /* 綁定request queue的make_request_fn函數到nvme_make_request */
    blk_queue_make_request(ns->queue, nvme_make_request);
    ns->dev = dev;
    ns->queue->queuedata = ns;

    /* 分配一個gendisk結構,gendisk用於描述一個塊設備 */
    disk = alloc_disk(0);
    if (!disk)
        goto out_free_queue;
    ns->ns_id = nsid;
    ns->disk = disk;
    lbaf = id->flbas & 0xf;
    ns->lba_shift = id->lbaf[lbaf].ds;
    ns->ms = le16_to_cpu(id->lbaf[lbaf].ms);
    /* 物理sector的大小,用戶看到的邏輯sector大小一般是512B,而物理sector大小不同廠商不同定義,可能跟一個nand flash page一樣,也可能小於一個nand flash page */
    blk_queue_logical_block_size(ns->queue, 1 << ns->lba_shift);
    /* 設備允許的一次request支持最大sector數量,request中的sector數量不能超過此值 */
    if (dev->max_hw_sectors)
        blk_queue_max_hw_sectors(ns->queue, dev->max_hw_sectors);
    if (dev->vwc & NVME_CTRL_VWC_PRESENT)
        blk_queue_flush(ns->queue, REQ_FLUSH | REQ_FUA);

    disk->major = nvme_major;
    disk->first_minor = 0;
    /* 此塊設備的操作函數 */
    disk->fops = &nvme_fops;
    disk->private_data = ns;
    /* 將上面初始化好的request queue與gendisk聯系一起 */
    disk->queue = ns->queue;
    disk->driverfs_dev = &dev->pci_dev->dev;
    /* 標記為允許擴展的設備,暫時不清楚什么意思 */
    disk->flags = GENHD_FL_EXT_DEVT;
    /* 在/dev/下顯示的名字 */
    sprintf(disk->disk_name, "nvme%dn%d", dev->instance, nsid);
    /* 設置用戶可用容量 */
    set_capacity(disk, le64_to_cpup(&id->nsze) << (ns->lba_shift - 9));

    /* 如果此nvme盤支持discard操作,則設置discard的一些初始參數,如discard必須以物理sector大小對齊 */
    if (dev->oncs & NVME_CTRL_ONCS_DSM)
        nvme_config_discard(ns);

    return ns;

 out_free_queue:
    blk_cleanup_queue(ns->queue);
 out_free_ns:
    kfree(ns);
    return NULL;
}

  這里主要初始化gendisk和request queue,gendisk用於描述一個塊設備,也就是當gendisk初始化好后,並調用add_disk(),就會在/dev/下出現一個此gendisk->name的塊設備.而request_queue有什么用呢,注意看gendisk初始化時,會將gendisk->queue設置為一個初始化好的request_queue.對於request_queue,最重要的是初始化一個make_request_fn的函數指針,當有進程對此gendisk對應的塊設備進行讀寫時,最終都會調用到gendisk的request_queue的make_request_fn所指的函數.在nvme驅動中,主要將request_queue的make_request_fn初始化為了nvme_make_request()函數,未來在說nvme設備的讀寫流程時,會詳細說明此函數.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM