当前位置: 首页 > news >正文

潍坊网站建设多少钱项目从立项到结束的流程图

潍坊网站建设多少钱,项目从立项到结束的流程图,常见的网站推广方式,网站源码修复本文主要参考这里 1’ 2 的解析和 linux 源码 3。 此处推荐一个可以便捷查看 linux 源码的网站 bootlin 4。 更新#xff1a;2022 / 02 / 11 驱动 | Linux | NVMe 不完全总结NVMe 的前世今生从系统角度看 NVMe 驱动NVMe CommandPCI 总线从架构角度看 NVMe 驱动NVMe 驱动的文件…本文主要参考这里 1’ 2 的解析和 linux 源码 3。 此处推荐一个可以便捷查看 linux 源码的网站 bootlin 4。 更新2022 / 02 / 11 驱动 | Linux | NVMe 不完全总结NVMe 的前世今生从系统角度看 NVMe 驱动NVMe CommandPCI 总线从架构角度看 NVMe 驱动NVMe 驱动的文件构成NVMe Driver 工作原理core.cnvme_core_initnvme_dev_fopsnvme_dev_opennvme_dev_releasenvme_dev_ioctlNVME_IO_RESETNVME_IOCTL_SUBSYS_RESETpci.cnvme_initBUILD_BUG_ONpci_register_drivernvme_id_tablenvme_probenvme_pci_alloc_devnvme_dev_mapnvme_setup_prp_poolsnvme_init_ctrl_finishDMA参考链接NVMe 的前世今生 NVMe 离不开 PCIeNVMe SSD 是 PCIe 的 endpoint。PCIe 是 x86 平台上一种流行的外设总线由于其 Plug and Play 的特性目前很多外设都通过 PCI Bus 与 Host 通信甚至不少CPU 的集成外设都通过 PCI Bus 连接如 APIC 等。 NVMe SSD 在 PCIe 接口上使用新的标准协议 NVMe由大厂 Intel 推出并交由 nvmexpress 组织推广现在被全球大部分存储企业采纳。 从系统角度看 NVMe 驱动 NVMe Command NVMe Host Server 和 NVMe Controller SSD 通过 NVMe Command 进行信息交互。NVMe Spec 中定义了 NVMe Command 的格式占用 64 字节。 NVMe Command 分为 Admin Command 和 IO Command 两大类前者主要是用于配置后者用于数据传输。 NVMe Command 是 Host 与 SSD Controller 交流的基本单元应用的 I/O 请求也要转化成NVMe Command。 或许可以将 NVMe Command 理解为英语host和SSD controller分别为韩国人和日本人这两个 对象 需要通过英语统一语法来进行彼此的沟通和交流Admin Command 是语法 负责语句的主谓宾结构IO Command是单词 构成语句的具体使用词语。一句话需要语法组织语句结构和单词作为语句的填充。 PCI 总线 在操作系统启动时BIOS 会枚举整个 PCI 的总线之后将扫描到的设备通过 ACPI tables 传给操作系统。当操作系统加载时PCI Bus 驱动则会根据此信息读取各个 PCI 设备的 PCI Header Config 空间从 class code 寄存器获得一个特征值。 class code 是 PCI bus 用来选择哪个驱动加载设备的唯一根据。 NVMe Spec 定义的 class code 是 010802h。NVMe SSD 内部的 Controller PCIe Header 中 class code 都会设置成010802h。 所以需要在驱动中指定 class code 为 010802h将 010802h 放入 pci_driver nvme_driver 的id_table。 之后当 nvme_driver 注册到 PCI Bus 后PCI Bus 就知道这个驱动是给 class code010802h 的设备使用的。 nvme_driver 中有一个 probe 函数nvme_probe()这个函数才是真正加载设备的处理函数。 从架构角度看 NVMe 驱动 学习 Linux NVMe Driver之前先看一下 Driver 在 Linux 架构中的位置如下图所示 NVMe driver 在 Block Layer 之下负责与 NVMe 设备交互。 为了紧跟时代的大趋势现在的 NVMe driver 已经很强大了也可以支持 NVMe over Fabric 相关设备如下图所示 不过本文还是以 NVMe over PCIe 为主。 NVMe 驱动的文件构成 最新的代码位于 linux/drivers/nvme/ 5。 其文件目录构成如下所示 nvme ----在分析一个 driver 时最好先看这个 driver 相关的 kconfig 及 Makefile 文件了解其文件架构再阅读相关的 source code。 Kconfig 文件的作用是 控制 make menuconfig 时出现的配置选项根据用户配置界面的选择将配置结果保存在 .config 配置文件该文件将提供给 Makefile 使用用以决定要编译的内核组件以及如何编译 先看一下 linux/drivers/nvme/host/Kconfig 6 的内容(NVMeOF相关内容已省略后续不再注明)如下所示 # SPDX-License-Identifier: GPL-2.0-only config NVME_COREtristateselect BLK_DEV_INTEGRITY_T10 if BLK_DEV_INTEGRITY ......接着再看一下 linux/drivers/nvme/host/Makefile 7 的内容如下所示 # SPDX-License-Identifier: GPL-2.0ccflags-y -I$(src)obj-$(CONFIG_NVME_CORE) nvme-core.o obj-$(CONFIG_BLK_DEV_NVME) nvme.o obj-$(CONFIG_NVME_FABRICS) nvme-fabrics.o obj-$(CONFIG_NVME_RDMA) nvme-rdma.o obj-$(CONFIG_NVME_FC) nvme-fc.o obj-$(CONFIG_NVME_TCP) nvme-tcp.o obj-$(CONFIG_NVME_APPLE) nvme-apple.o ......从 Kconfig 和 Makefile 来看了解 NVMe over PCIe 相关的知识点我们主要关注 core.cpci.c 就好。 NVMe Driver 工作原理 core.c 通过该文件了解 NVMe 驱动可以如何操作 NVMe 设备。 nvme_core_init 从 linux/drivers/nvme/host/core.c 8 找到程序入口 module_init(nvme_core_init);如下所示 static int __init nvme_core_init(void) {int result -ENOMEM; ......// 1. 注册字符设备 nvmeresult alloc_chrdev_region(nvme_ctrl_base_chr_devt, 0,NVME_MINORS, nvme);if (result 0)goto destroy_delete_wq;// 2. 新建一个nvme class拥有者(Owner)是为THIS_MODULE// 如果有Error发生删除字符设备nvmenvme_class class_create(THIS_MODULE, nvme); if (IS_ERR(nvme_class)) {result PTR_ERR(nvme_class);goto unregister_chrdev;}nvme_class-dev_uevent nvme_class_uevent;nvme_subsys_class class_create(THIS_MODULE, nvme-subsystem);if (IS_ERR(nvme_subsys_class)) {result PTR_ERR(nvme_subsys_class);goto destroy_class;}result alloc_chrdev_region(nvme_ns_chr_devt, 0, NVME_MINORS,nvme-generic);if (result 0)goto destroy_subsys_class;nvme_ns_chr_class class_create(THIS_MODULE, nvme-generic);if (IS_ERR(nvme_ns_chr_class)) {result PTR_ERR(nvme_ns_chr_class);goto unregister_generic_ns;}result nvme_init_auth();if (result)goto destroy_ns_chr;return 0;从上面来看nvme_core_init 主要做了两件事 调用 alloc_chrdev_region 9 函数如下所示注册一个名为 nvme 的字符设备。 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name) {struct char_device_struct *cd;cd __register_chrdev_region(0, baseminor, count, name);if (IS_ERR(cd))return PTR_ERR(cd);*dev MKDEV(cd-major, cd-baseminor);return 0; }调用 class_create 函数 10如下所示动态创建设备的逻辑类并完成部分字段的初始化然后将其添加到内核中。创建的逻辑类位于 /sys/class/。 #define class_create(owner, name) \ ({ \static struct lock_class_key __key; \__class_create(owner, name, __key); \ })#endif /* _DEVICE_CLASS_H_ */在注册字符设备时涉及到了设备号的知识点 一个字符设备或者块设备都有一个主设备号 Major 和次设备号 Minor 。主设备号用来表示一个特定的驱动程序次设备号用来表示使用该驱动程序的各个设备。比如我们在 Linux 系统上挂了两块 NVMe SSD那么主设备号就可以自动分配一个数字 (比如 8 次设备号分别为 1 和 2。 例如在 32 位机子中设备号共 32 位高 12 位表示主设备号低 20 位表示次设备号。 nvme_dev_fops 通过 nvme_core_init 进行字符设备的注册后我们就可以通过 nvme_dev_fops 中的 openioctrlrelease 接口对其进行操作了。 nvme 字符设备的文件操作结构体 nvme_dev_fops 8 定义如下: static const struct file_operations nvme_dev_fops {.owner THIS_MODULE,// open()用来打开一个设备在该函数中可以对设备进行初始化。.open nvme_dev_open, // release()用来释放open()函数中申请的资源。 .release nvme_dev_release,// nvme_dev_ioctl()提供一种执行设备特定命令的方法。// 这里的ioctl有两个unlocked_ioctl和compat_ioctl。// 如果这两个同时存在的话优先调用unlocked_ioctl。// 对于compat_ioctl只有打来了CONFIG_COMPAT 才会调用compat_ioctl。*/.unlocked_ioctl nvme_dev_ioctl,.compat_ioctl compat_ptr_ioctl,.uring_cmd nvme_dev_uring_cmd, };nvme_dev_open nvme_dev_open 8 的定义如下 static int nvme_dev_open(struct inode *inode, struct file *file) {// 从incode中获取次设备号struct nvme_ctrl *ctrl container_of(inode-i_cdev, struct nvme_ctrl, cdev); switch (ctrl-state) {case NVME_CTRL_LIVE:break;default:// -EWOULDBLOCK: Operation would blockreturn -EWOULDBLOCK;}nvme_get_ctrl(ctrl);if (!try_module_get(ctrl-ops-module)) {nvme_put_ctrl(ctrl);// -EINVAL: Invalid argumentreturn -EINVAL;}file-private_data ctrl;return 0; }找到与 inode 次设备号对应的 nvme 设备接着判断 nvme_ctrl_live 和 module最后找到的 nvme 设备放到 file-private_data 区域。 nvme_dev_release nvme_dev_release 的定义如下 static int nvme_dev_release(struct inode *inode, struct file *file) {struct nvme_ctrl *ctrl container_of(inode-i_cdev, struct nvme_ctrl, cdev);module_put(ctrl-ops-module);nvme_put_ctrl(ctrl);return 0; }用来释放 nvme_dev_open 中申请的资源。 nvme_dev_ioctl nvme_dev_ioctl 11 的定义如下 long nvme_dev_ioctl(struct file *file, unsigned int cmd,unsigned long arg) {struct nvme_ctrl *ctrl file-private_data;void __user *argp (void __user *)arg;switch (cmd) {case NVME_IOCTL_ADMIN_CMD:return nvme_user_cmd(ctrl, NULL, argp, 0, file-f_mode);case NVME_IOCTL_ADMIN64_CMD:return nvme_user_cmd64(ctrl, NULL, argp, 0, file-f_mode);case NVME_IOCTL_IO_CMD:return nvme_dev_user_cmd(ctrl, argp, file-f_mode);case NVME_IOCTL_RESET:if (!capable(CAP_SYS_ADMIN))return -EACCES;dev_warn(ctrl-device, resetting controller\n);return nvme_reset_ctrl_sync(ctrl);case NVME_IOCTL_SUBSYS_RESET:if (!capable(CAP_SYS_ADMIN))return -EACCES;return nvme_reset_subsystem(ctrl);case NVME_IOCTL_RESCAN:if (!capable(CAP_SYS_ADMIN))return -EACCES;nvme_queue_scan(ctrl);return 0;default:// 错误的ioctrl命令return -ENOTTY;} }从上面可以发现在 nvme_dev_ioctl 中总共实现了 5 种 Command 命令解释NVME_IOCTL_ADMIN_CMDNVMe Spec 定义的 Admin 命令。NVME_IOCTL_IO_CMDNVMe Spec 定义的 IO 命令。不支持 NVMe 设备有多个namespace 的情况。NVME_IOCTL_RESETNVME_IOCTL_SUBSYS_RESET通过向相关 bar 寄存器写入指定值 0x4E564D65 来完成NVME_IOCTL_RESCANNVME_IO_RESET 先来看看 nvme_reset_ctrl_sync 的定义如下 int nvme_reset_ctrl_sync(struct nvme_ctrl *ctrl) {int ret;ret nvme_reset_ctrl(ctrl);if (!ret) {flush_work(ctrl-reset_work);if (ctrl-state ! NVME_CTRL_LIVE)// ENETRESET: Network dropped connection because of reset */ret -ENETRESET;}return ret; }再来看看 nvme_reset_ctrl如下 int nvme_reset_ctrl(struct nvme_ctrl *ctrl) {if (!nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))return -EBUSY;if (!queue_work(nvme_reset_wq, ctrl-reset_work))// EBUSH: Device or resource busyreturn -EBUSY;return 0; } EXPORT_SYMBOL_GPL(nvme_reset_ctrl);从上面的定义中可以了解到如果通过调用 nvme_reset_ctrl 获取当前设备和资源的状态 如果处于 busy 状态则不进行相关的 reset 的动作如果处于空闲状态则调用 flush_work(ctrl-reset_work) 来 reset nvme controller。 再来看一下 flush_work 的定义如下 static bool __flush_work(struct work_struct *work, bool from_cancel) {struct wq_barrier barr;if (WARN_ON(!wq_online))return false;if (WARN_ON(!work-func))return false;lock_map_acquire(work-lockdep_map);lock_map_release(work-lockdep_map);if (start_flush_work(work, barr, from_cancel)) {wait_for_completion(barr.done);destroy_work_on_stack(barr.work);return true;} else {return false;} }/*** flush_work - wait for a work to finish executing the last queueing instance* work: the work to flush** Wait until work has finished execution. work is guaranteed to be idle* on return if it hasnt been requeued since flush started.** Return:* %true if flush_work() waited for the work to finish execution,* %false if it was already idle.*/ bool flush_work(struct work_struct *work) {return __flush_work(work, false); } EXPORT_SYMBOL_GPL(flush_work);NVME_IOCTL_SUBSYS_RESET 先来看看 nvme_reset_subsystem 的定义如下 static inline int nvme_reset_subsystem(struct nvme_ctrl *ctrl) {int ret;if (!ctrl-subsystem)return -ENOTTY;if (!nvme_wait_reset(ctrl))// EBUSH: Device or resource busyreturn -EBUSY;ret ctrl-ops-reg_write32(ctrl, NVME_REG_NSSR, 0x4E564D65);if (ret)return ret;return nvme_try_sched_reset(ctrl); }先来看一下 nvme_wait_reset 的定义如下 /** Waits for the controller state to be resetting, or returns false if it is* not possible to ever transition to that state.*/ bool nvme_wait_reset(struct nvme_ctrl *ctrl) {wait_event(ctrl-state_wq,nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING) ||nvme_state_terminal(ctrl));return ctrl-state NVME_CTRL_RESETTING; } EXPORT_SYMBOL_GPL(nvme_wait_reset);通过 nvme_wait_reset 获取当前设备或资源的状态 如果 controller state 是 resetting说明当前设备或资源处于繁忙状态无法完成 nvme_reset_subsystem如果 controller state 是 resetting通过 ctrl-ops-reg_write32 对 NVME_REG_NSSR 写入 0x4E564D65 来完成 nvme_reset_subsystem。 再来看一下 reg_write32 的定义 struct nvme_ctrl_ops {const char *name;struct module *module;unsigned int flags; #define NVME_F_FABRICS (1 0) #define NVME_F_METADATA_SUPPORTED (1 1) #define NVME_F_BLOCKING (1 2)const struct attribute_group **dev_attr_groups;int (*reg_read32)(struct nvme_ctrl *ctrl, u32 off, u32 *val);int (*reg_write32)(struct nvme_ctrl *ctrl, u32 off, u32 val);int (*reg_read64)(struct nvme_ctrl *ctrl, u32 off, u64 *val);void (*free_ctrl)(struct nvme_ctrl *ctrl);void (*submit_async_event)(struct nvme_ctrl *ctrl);void (*delete_ctrl)(struct nvme_ctrl *ctrl);void (*stop_ctrl)(struct nvme_ctrl *ctrl);int (*get_address)(struct nvme_ctrl *ctrl, char *buf, int size);void (*print_device_info)(struct nvme_ctrl *ctrl);bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl); };从上面不难看出reg_write32 是 nvme_ctrl_ops 的其中一种操作。 再看一下 nvme_ctrl_ops 的定义如下 static const struct nvme_ctrl_ops nvme_pci_ctrl_ops {.name pcie,.module THIS_MODULE,.flags NVME_F_METADATA_SUPPORTED,.dev_attr_groups nvme_pci_dev_attr_groups,.reg_read32 nvme_pci_reg_read32,.reg_write32 nvme_pci_reg_write32,.reg_read64 nvme_pci_reg_read64,.free_ctrl nvme_pci_free_ctrl,.submit_async_event nvme_pci_submit_async_event,.get_address nvme_pci_get_address,.print_device_info nvme_pci_print_device_info,.supports_pci_p2pdma nvme_pci_supports_pci_p2pdma, };从上面看reg_write32 相当于 nvme_pci_reg_write32。 那么再看 nvme_pci_reg_write32 的定义即 static int nvme_pci_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val) {writel(val, to_nvme_dev(ctrl)-bar off);return 0; }static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl) {return container_of(ctrl, struct nvme_dev, ctrl); }从上面看reg_write32 先通过调用 to_nvme_dev 获取 nvme_ctrl 的地址并赋值于nvme_dev而后 writel 向指定 bar 寄存器中写入指定的 val 值。 pci.c 通过该文件可以了解 NVMe 驱动的初始化是怎样进行的。 nvme_init 从 linux/drivers/nvme/host/pci.c 12 找到程序入口 module_init(nvme_init);如下所示 static int __init nvme_init(void) {BUILD_BUG_ON(sizeof(struct nvme_create_cq) ! 64);BUILD_BUG_ON(sizeof(struct nvme_create_sq) ! 64);BUILD_BUG_ON(sizeof(struct nvme_delete_queue) ! 64);BUILD_BUG_ON(IRQ_AFFINITY_MAX_SETS 2);BUILD_BUG_ON(DIV_ROUND_UP(nvme_pci_npages_prp(), NVME_CTRL_PAGE_SIZE) S8_MAX);// 注册NVMe驱动return pci_register_driver(nvme_driver); }从上面来看初始化的过程只进行了两步 通过 BUILD_BUG_ON 检测一些相关参数是否为异常值通过 pci_register_driver 注册 NVMe 驱动 BUILD_BUG_ON 先看其定义 13如下 /*** BUILD_BUG_ON - break compile if a condition is true.* condition: the condition which the compiler should know is false.** If you have some code which relies on certain constants being equal, or* some other compile-time-evaluated condition, you should use BUILD_BUG_ON to* detect if someone changes it.*/ #define BUILD_BUG_ON(condition) \BUILD_BUG_ON_MSG(condition, BUILD_BUG_ON failed: #condition)所以在初始化阶段会通过 BUILD_BUG_ON 监测 nvme_create_cqnvme_create_sqnvme_delete_queue 等参数是否同预期值一致。 pci_register_driver 先看其定义 14如下 static inline int pci_register_driver(struct pci_driver *drv) { return 0; }/* pci_register_driver() must be a macro so KBUILD_MODNAME can be expanded */ #define pci_register_driver(driver) \__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)在初始化过程中调用 pci_register_driver 函数将 nvme_driver 注册到 PCI Bus。 然后PCI Bus 是怎么将 NVMe 驱动匹配到对应的 NVMe 设备的呢 nvme_id_table 系统启动时BIOS 会枚举整个 PCI Bus, 之后将扫描到的设备通过 ACPI tables 传给操作系统。 当操作系统加载时PCI Bus 驱动则会根据此信息读取各个 PCI 设备的 PCIe Header Config 空间从 class code 寄存器获得一个特征值。 class code 就是 PCI bus 用来选择哪个驱动加载设备的唯一根据。 NVMe Spec 定义 NVMe 设备的 Class code0x010802h, 如下图。 然后nvme driver 会将 class code 写入 nvme_id_table 12如下所示 static struct pci_driver nvme_driver {.name nvme,.id_table nvme_id_table,.probe nvme_probe,.remove nvme_remove,.shutdown nvme_shutdown,.driver {.probe_type PROBE_PREFER_ASYNCHRONOUS, #ifdef CONFIG_PM_SLEEP.pm nvme_dev_pm_ops, #endif},.sriov_configure pci_sriov_configure_simple,.err_handler nvme_err_handler, };nvme_id_table 的内容如下 static const struct pci_device_id nvme_id_table[] {{ PCI_VDEVICE(INTEL, 0x0953), /* Intel 750/P3500/P3600/P3700 */.driver_data NVME_QUIRK_STRIPE_SIZE |NVME_QUIRK_DEALLOCATE_ZEROES, },......{ PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2005),.driver_data NVME_QUIRK_SINGLE_VECTOR |NVME_QUIRK_128_BYTES_SQES |NVME_QUIRK_SHARED_TAGS |NVME_QUIRK_SKIP_CID_GEN |NVME_QUIRK_IDENTIFY_CNS },{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },{ 0, } }; MODULE_DEVICE_TABLE(pci, nvme_id_table);再看一下 PCI_CLASS_STORAGE_EXPRESS 的定义如下 /*** PCI_DEVICE_CLASS - macro used to describe a specific PCI device class* dev_class: the class, subclass, prog-if triple for this device* dev_class_mask: the class mask for this device** This macro is used to create a struct pci_device_id that matches a* specific PCI class. The vendor, device, subvendor, and subdevice* fields will be set to PCI_ANY_ID.*/ #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 设备的宏变量。 在 PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) 中的 PCI_CLASS_STORAGE_EXPRESS 的值遵循 NVMe Spec 中定义的 Class code0x010802h在 linux/include/linux/pci_ids.h 15 中有所体现如下所示 #define PCI_CLASS_STORAGE_EXPRESS 0x010802因此pci_register_driver 将 nvme_driver 注册到 PCI Bus 之后PCI Bus 就明白了这个驱动是给NVMe 设备( Class code0x010802h ) 用的。 到这里只是找到 PCI Bus 上面驱动与 NVMe 设备的对应关系。 nvme_init 执行完毕返回后nvme 驱动就啥事不做了直到 PCI 总线枚举出了这个 nvme 设备就开始调用 nvme_probe() 函数开始 “干活”。 nvme_probe 现在再回忆一下 nvme_driver 的结构体如下所示 static struct pci_driver nvme_driver {.name nvme,.id_table nvme_id_table,.probe nvme_probe,.remove nvme_remove,.shutdown nvme_shutdown,.driver {.probe_type PROBE_PREFER_ASYNCHRONOUS, #ifdef CONFIG_PM_SLEEP.pm nvme_dev_pm_ops, #endif},.sriov_configure pci_sriov_configure_simple,.err_handler nvme_err_handler, };那么现在看一下 nvme_probe 都做了什么如下所示 static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) {struct nvme_dev *dev;int result -ENOMEM;// 1.dev nvme_pci_alloc_dev(pdev, id);if (!dev)return -ENOMEM;// 2. 获得PCI Bar的虚拟地址result nvme_dev_map(dev);if (result)goto out_uninit_ctrl;// 3. 设置 DMA 需要的 PRP 内存池result nvme_setup_prp_pools(dev);if (result)goto out_dev_unmap;// 4.result nvme_pci_alloc_iod_mempool(dev);if (result)goto out_release_prp_pools;// 5. 打印日志dev_info(dev-ctrl.device, pci function %s\n, dev_name(pdev-dev));// 6.result nvme_pci_enable(dev);if (result)goto out_release_iod_mempool;// 7.result nvme_alloc_admin_tag_set(dev-ctrl, dev-admin_tagset,nvme_mq_admin_ops, sizeof(struct nvme_iod));if (result)goto out_disable;/** Mark the controller as connecting before sending admin commands to* allow the timeout handler to do the right thing.*/// 8.if (!nvme_change_ctrl_state(dev-ctrl, NVME_CTRL_CONNECTING)) {dev_warn(dev-ctrl.device,failed to mark controller CONNECTING\n);result -EBUSY;goto out_disable;}// 9. 初始化NVMe Controller结构result nvme_init_ctrl_finish(dev-ctrl, false);if (result)goto out_disable;// 10.nvme_dbbuf_dma_alloc(dev);// 11.result nvme_setup_host_mem(dev);if (result 0)goto out_disable;// 12.result nvme_setup_io_queues(dev);if (result)goto out_disable;// 13.if (dev-online_queues 1) {nvme_alloc_io_tag_set(dev-ctrl, dev-tagset, nvme_mq_ops,nvme_pci_nr_maps(dev), sizeof(struct nvme_iod));nvme_dbbuf_set(dev);}// 14.if (!dev-ctrl.tagset)dev_warn(dev-ctrl.device, IO queues not created\n);// 15.if (!nvme_change_ctrl_state(dev-ctrl, NVME_CTRL_LIVE)) {dev_warn(dev-ctrl.device,failed to mark controller live state\n);result -ENODEV;goto out_disable;}// 16. 为设备设置私有数据指针 pci_set_drvdata(pdev, dev);nvme_start_ctrl(dev-ctrl);nvme_put_ctrl(dev-ctrl);flush_work(dev-ctrl.scan_work);return 0;out_disable:nvme_change_ctrl_state(dev-ctrl, NVME_CTRL_DELETING);nvme_dev_disable(dev, true);nvme_free_host_mem(dev);nvme_dev_remove_admin(dev);nvme_dbbuf_dma_free(dev);nvme_free_queues(dev, 0); out_release_iod_mempool:mempool_destroy(dev-iod_mempool); out_release_prp_pools:nvme_release_prp_pools(dev); out_dev_unmap:nvme_dev_unmap(dev); out_uninit_ctrl:nvme_uninit_ctrl(dev-ctrl);return result; }下面针对 nvme_probe 过程中的一些函数的使用进行进一步分析 nvme_pci_alloc_dev 先看它的定义如下 static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev,const struct pci_device_id *id) {unsigned long quirks id-driver_data;// 通过调用 dev_to_node 得到这个 pci_dev 的 numa 节点。// 如果没有制定的话默认用 first_memory_node也就是第一个 numa 节点. int node dev_to_node(pdev-dev);struct nvme_dev *dev;int ret -ENOMEM;if (node NUMA_NO_NODE)set_dev_node(pdev-dev, first_memory_node);// 为 nvme dev 节点分配空间dev kzalloc_node(sizeof(*dev), GFP_KERNEL, node);if (!dev)return NULL;// 初始化两个work变量, 放在nvme_workq中执行// 初始化互斥锁INIT_WORK(dev-ctrl.reset_work, nvme_reset_work);mutex_init(dev-shutdown_lock);// 分配queuedev-nr_write_queues write_queues;dev-nr_poll_queues poll_queues;dev-nr_allocated_queues nvme_max_io_queues(dev) 1;dev-queues kcalloc_node(dev-nr_allocated_queues,sizeof(struct nvme_queue), GFP_KERNEL, node);if (!dev-queues)goto out_free_dev;// 增加设备对象的引用计数dev-dev get_device(pdev-dev);quirks | check_vendor_combination_bug(pdev);if (!noacpi acpi_storage_d3(pdev-dev)) {/** Some systems use a bios work around to ask for D3 on* platforms that support kernel managed suspend.*/dev_info(pdev-dev,platform quirk: setting simple suspend\n);quirks | NVME_QUIRK_SIMPLE_SUSPEND;}// 初始化 NVMe Controller 结构ret nvme_init_ctrl(dev-ctrl, pdev-dev, nvme_pci_ctrl_ops,quirks);if (ret)goto out_put_device;dma_set_min_align_mask(pdev-dev, NVME_CTRL_PAGE_SIZE - 1);dma_set_max_seg_size(pdev-dev, 0xffffffff);/** Limit the max command size to prevent iod-sg allocations going* over a single page.*/dev-ctrl.max_hw_sectors min_t(u32,NVME_MAX_KB_SZ 1, dma_max_mapping_size(pdev-dev) 9);dev-ctrl.max_segments NVME_MAX_SEGS;/** There is no support for SGLs for metadata (yet), so we are limited to* a single integrity segment for the separate metadata pointer.*/dev-ctrl.max_integrity_segments 1;return dev;out_put_device:put_device(dev-dev);kfree(dev-queues); out_free_dev:kfree(dev);return ERR_PTR(ret); }再看一下 nvme_init_ctrl 的定义如下 /** Initialize a NVMe controller structures. This needs to be called during* earliest initialization so that we have the initialized structured around* during probing.*/ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,const struct nvme_ctrl_ops *ops, unsigned long quirks) {int ret;ctrl-state NVME_CTRL_NEW;clear_bit(NVME_CTRL_FAILFAST_EXPIRED, ctrl-flags);spin_lock_init(ctrl-lock);mutex_init(ctrl-scan_lock);INIT_LIST_HEAD(ctrl-namespaces);xa_init(ctrl-cels);init_rwsem(ctrl-namespaces_rwsem);ctrl-dev dev;ctrl-ops ops;ctrl-quirks quirks;ctrl-numa_node NUMA_NO_NODE;INIT_WORK(ctrl-scan_work, nvme_scan_work);INIT_WORK(ctrl-async_event_work, nvme_async_event_work);INIT_WORK(ctrl-fw_act_work, nvme_fw_act_work);INIT_WORK(ctrl-delete_work, nvme_delete_ctrl_work);init_waitqueue_head(ctrl-state_wq);INIT_DELAYED_WORK(ctrl-ka_work, nvme_keep_alive_work);INIT_DELAYED_WORK(ctrl-failfast_work, nvme_failfast_work);memset(ctrl-ka_cmd, 0, sizeof(ctrl-ka_cmd));ctrl-ka_cmd.common.opcode nvme_admin_keep_alive;BUILD_BUG_ON(NVME_DSM_MAX_RANGES * sizeof(struct nvme_dsm_range) PAGE_SIZE);ctrl-discard_page alloc_page(GFP_KERNEL);if (!ctrl-discard_page) {ret -ENOMEM;goto out;}ret ida_alloc(nvme_instance_ida, GFP_KERNEL);if (ret 0)goto out;ctrl-instance ret;device_initialize(ctrl-ctrl_device);ctrl-device ctrl-ctrl_device;ctrl-device-devt MKDEV(MAJOR(nvme_ctrl_base_chr_devt),ctrl-instance);ctrl-device-class nvme_class;ctrl-device-parent ctrl-dev;if (ops-dev_attr_groups)ctrl-device-groups ops-dev_attr_groups;elsectrl-device-groups nvme_dev_attr_groups;ctrl-device-release nvme_free_ctrl;dev_set_drvdata(ctrl-device, ctrl);ret dev_set_name(ctrl-device, nvme%d, ctrl-instance);if (ret)goto out_release_instance;nvme_get_ctrl(ctrl);cdev_init(ctrl-cdev, nvme_dev_fops);ctrl-cdev.owner ops-module;ret cdev_device_add(ctrl-cdev, ctrl-device);if (ret)goto out_free_name;/** Initialize latency tolerance controls. The sysfs files wont* be visible to userspace unless the device actually supports APST.*/ctrl-device-power.set_latency_tolerance nvme_set_latency_tolerance;dev_pm_qos_update_user_latency_tolerance(ctrl-device,min(default_ps_max_latency_us, (unsigned long)S32_MAX));nvme_fault_inject_init(ctrl-fault_inject, dev_name(ctrl-device));nvme_mpath_init_ctrl(ctrl);ret nvme_auth_init_ctrl(ctrl);if (ret)goto out_free_cdev;return 0; out_free_cdev:cdev_device_del(ctrl-cdev, ctrl-device); out_free_name:nvme_put_ctrl(ctrl);kfree_const(ctrl-device-kobj.name); out_release_instance:ida_free(nvme_instance_ida, ctrl-instance); out:if (ctrl-discard_page)__free_page(ctrl-discard_page);return ret; } EXPORT_SYMBOL_GPL(nvme_init_ctrl);从上面的代码中我们可以了解到nvme_init_ctrl 的作用主要是调用 dev_set_name 函数创建一个名字叫 nvmex 的字符设备。 下面看一下 dev_set_name 的定义如下 /*** dev_set_name - set a device name* dev: device* fmt: format string for the devices name*/ int dev_set_name(struct device *dev, const char *fmt, ...) {va_list vargs;int err;va_start(vargs, fmt);err kobject_set_name_vargs(dev-kobj, fmt, vargs);va_end(vargs);return err; } EXPORT_SYMBOL_GPL(dev_set_name);这个 nvmex 中的 x 是通过 kobject_set_name_vargs 获得唯一的索引值。 再来看一下 kobject_set_name_vargs 的实现如下 /*** kobject_set_name_vargs() - Set the name of a kobject.* kobj: struct kobject to set the name of* fmt: format string used to build the name* vargs: vargs to format the string.*/ int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,va_list vargs) {const char *s;if (kobj-name !fmt)return 0;s kvasprintf_const(GFP_KERNEL, fmt, vargs);if (!s)return -ENOMEM;/** ewww... some of these buggers have / in the name ... If* thats the case, we need to make sure we have an actual* allocated copy to modify, since kvasprintf_const may have* returned something from .rodata.*/if (strchr(s, /)) {char *t;t kstrdup(s, GFP_KERNEL);kfree_const(s);if (!t)return -ENOMEM;strreplace(t, /, !);s t;}kfree_const(kobj-name);kobj-name s;return 0; }nvme_dev_map 先看它的定义如下 static int nvme_dev_map(struct nvme_dev *dev) {struct pci_dev *pdev to_pci_dev(dev-dev);if (pci_request_mem_regions(pdev, nvme))return -ENODEV;if (nvme_remap_bar(dev, NVME_REG_DBS 4096))goto release;return 0;release:pci_release_mem_regions(pdev);return -ENODEV; }再看一下 pci_request_mem_regions 14 的定义如下 static inline int pci_request_mem_regions(struct pci_dev *pdev, const char *name) {return pci_request_selected_regions(pdev,pci_select_bars(pdev, IORESOURCE_MEM), name); }/*** pci_select_bars - Make BAR mask from the type of resource* dev: the PCI device for which BAR mask is made* flags: resource type mask to be selected** This helper routine makes bar mask from the type of resource.*/ int pci_select_bars(struct pci_dev *dev, unsigned long flags) {int i, bars 0;for (i 0; i PCI_NUM_RESOURCES; i)if (pci_resource_flags(dev, i) flags)bars | (1 i);return bars; } EXPORT_SYMBOL(pci_select_bars);综合上面代码可得知nvme_dev_map 的执行过程可以分为三步 调用 pci_select_bars其返回值为 mask。 因为 PCI 设备的 Header Config 有 6 个 32 位的Bar 寄存器(如下图)所以 mark 中的每一位的值就代表其中一个 Bar 是否被置起。 2. 调用 pci_request_selected_regions这个函数的一个参数就是之前调用 pci_select_bars 返回的mask 值作用就是把对应的这个几个 bar 保留起来不让别人使用。 3. 调用 nvme_remap_bar。在 linux 中我们无法直接访问物理地址需要映射到虚拟地址nvme_remap_bar 就是这个作用。映射完后我们访问 dev-bar 就可以直接操作 nvme 设备上的寄存器了。但是代码中并没有根据 pci_select_bars 的返回值来决定映射哪个 bar而是直接映射 bar0原因是 NVMe 协议中强制规定了 BAR0 就是内存映射的基址。 nvme_setup_prp_pools 先来看一下它的定义如下 static int nvme_setup_prp_pools(struct nvme_dev *dev) {dev-prp_page_pool dma_pool_create(prp list page, dev-dev,NVME_CTRL_PAGE_SIZE,NVME_CTRL_PAGE_SIZE, 0);if (!dev-prp_page_pool)return -ENOMEM;/* Optimisation for I/Os between 4k and 128k */dev-prp_small_pool dma_pool_create(prp list 256, dev-dev,256, 256, 0);if (!dev-prp_small_pool) {dma_pool_destroy(dev-prp_page_pool);return -ENOMEM;}return 0; }从上面的代码可以了解到 nvme_setup_prp_pools主要是创建了两个 dma pool prp_page_pool 提供的是块大小为 Page_Size (格式化时确定例如 4KB ) 的内存主要是为了对于不一样长度的 prp list 来做优化prp_small_pool 里提供的是块大小为 256 字节的内存 后面就可以通过其他 dma 函数从 dma pool 中获得 memory 了。 nvme_init_ctrl_finish DMA PCIe 有个寄存器位 Bus Master Enable这个 bit 置 1 后PCIe 设备就可以向Host 发送 DMA Read Memory 和 DMA Write Memory 请求。 当 Host 的 driver 需要跟 PCIe 设备传输数据的时候只需要告诉 PCIe 设备存放数据的地址就可以。 NVMe Command 占用 64 个字节另外其 PCIe BAR 空间被映射到虚拟内存空间其中包括用来通知 NVMe SSD Controller 读取 Command 的 Doorbell 寄存器。 NVMe 数据传输都是通过 NVMe Command而 NVMe Command 则存放在 NVMe Queue 中其配置如下图所示 其中队列中有 Submission QueueCompletion Queue 两个。 参考链接 NVMe驱动系列文章 ↩︎ Linux NVMe Driver学习笔记之1概述与nvme_core_init函数解析 ↩︎ linux ↩︎ bootlin ↩︎ linux/drivers/nvme/ ↩︎ linux/drivers/nvme/host/Kconfig ↩︎ linux/drivers/nvme/host/Makefile ↩︎ linux/drivers/nvme/host/core.c ↩︎ ↩︎ ↩︎ linux/fs/char_dev.c ↩︎ linux/include/linux/device.h ↩︎ linux/drivers/nvme/host/ioctl.c ↩︎ linux/drivers/nvme/host/pci.c ↩︎ ↩︎ linux/include/linux/build_bug.h ↩︎ linux/include/linux/pci.h ↩︎ ↩︎ linux/include/linux/pci_ids.h ↩︎
文章转载自:
http://www.morning.jbztm.cn.gov.cn.jbztm.cn
http://www.morning.dblfl.cn.gov.cn.dblfl.cn
http://www.morning.tgxrm.cn.gov.cn.tgxrm.cn
http://www.morning.zcckq.cn.gov.cn.zcckq.cn
http://www.morning.lmnbp.cn.gov.cn.lmnbp.cn
http://www.morning.bpds.cn.gov.cn.bpds.cn
http://www.morning.ktfbl.cn.gov.cn.ktfbl.cn
http://www.morning.sxcwc.cn.gov.cn.sxcwc.cn
http://www.morning.llsrg.cn.gov.cn.llsrg.cn
http://www.morning.hmbxd.cn.gov.cn.hmbxd.cn
http://www.morning.rdsst.cn.gov.cn.rdsst.cn
http://www.morning.rnhh.cn.gov.cn.rnhh.cn
http://www.morning.yrnll.cn.gov.cn.yrnll.cn
http://www.morning.schwr.cn.gov.cn.schwr.cn
http://www.morning.gwtbn.cn.gov.cn.gwtbn.cn
http://www.morning.rrms.cn.gov.cn.rrms.cn
http://www.morning.kfjnx.cn.gov.cn.kfjnx.cn
http://www.morning.bdkhl.cn.gov.cn.bdkhl.cn
http://www.morning.ghcfx.cn.gov.cn.ghcfx.cn
http://www.morning.kfyqd.cn.gov.cn.kfyqd.cn
http://www.morning.mqxrx.cn.gov.cn.mqxrx.cn
http://www.morning.nxbkw.cn.gov.cn.nxbkw.cn
http://www.morning.qgjxy.cn.gov.cn.qgjxy.cn
http://www.morning.slwfy.cn.gov.cn.slwfy.cn
http://www.morning.fbnsx.cn.gov.cn.fbnsx.cn
http://www.morning.dmzqd.cn.gov.cn.dmzqd.cn
http://www.morning.zwxfj.cn.gov.cn.zwxfj.cn
http://www.morning.pjzcp.cn.gov.cn.pjzcp.cn
http://www.morning.hrgxk.cn.gov.cn.hrgxk.cn
http://www.morning.mhwtq.cn.gov.cn.mhwtq.cn
http://www.morning.nwllb.cn.gov.cn.nwllb.cn
http://www.morning.czgtt.cn.gov.cn.czgtt.cn
http://www.morning.zhmgcreativeeducation.cn.gov.cn.zhmgcreativeeducation.cn
http://www.morning.bpcf.cn.gov.cn.bpcf.cn
http://www.morning.rtpw.cn.gov.cn.rtpw.cn
http://www.morning.pbsqr.cn.gov.cn.pbsqr.cn
http://www.morning.rnxw.cn.gov.cn.rnxw.cn
http://www.morning.ghlyy.cn.gov.cn.ghlyy.cn
http://www.morning.haibuli.com.gov.cn.haibuli.com
http://www.morning.xkhxl.cn.gov.cn.xkhxl.cn
http://www.morning.rycd.cn.gov.cn.rycd.cn
http://www.morning.tbplf.cn.gov.cn.tbplf.cn
http://www.morning.fcwxs.cn.gov.cn.fcwxs.cn
http://www.morning.mrtdq.cn.gov.cn.mrtdq.cn
http://www.morning.gqfjb.cn.gov.cn.gqfjb.cn
http://www.morning.fwgnq.cn.gov.cn.fwgnq.cn
http://www.morning.ptmgq.cn.gov.cn.ptmgq.cn
http://www.morning.dnmgr.cn.gov.cn.dnmgr.cn
http://www.morning.cxlys.cn.gov.cn.cxlys.cn
http://www.morning.bcdqf.cn.gov.cn.bcdqf.cn
http://www.morning.ztcwp.cn.gov.cn.ztcwp.cn
http://www.morning.nlrp.cn.gov.cn.nlrp.cn
http://www.morning.rmyt.cn.gov.cn.rmyt.cn
http://www.morning.wnwjf.cn.gov.cn.wnwjf.cn
http://www.morning.ryjl.cn.gov.cn.ryjl.cn
http://www.morning.lnmby.cn.gov.cn.lnmby.cn
http://www.morning.xbmwm.cn.gov.cn.xbmwm.cn
http://www.morning.gppqf.cn.gov.cn.gppqf.cn
http://www.morning.mdmc.cn.gov.cn.mdmc.cn
http://www.morning.nxbkw.cn.gov.cn.nxbkw.cn
http://www.morning.blznh.cn.gov.cn.blznh.cn
http://www.morning.jhgxh.cn.gov.cn.jhgxh.cn
http://www.morning.yhwxn.cn.gov.cn.yhwxn.cn
http://www.morning.ejknty.cn.gov.cn.ejknty.cn
http://www.morning.ykwbx.cn.gov.cn.ykwbx.cn
http://www.morning.knlgk.cn.gov.cn.knlgk.cn
http://www.morning.cokcb.cn.gov.cn.cokcb.cn
http://www.morning.hilmwmu.cn.gov.cn.hilmwmu.cn
http://www.morning.jhxtm.cn.gov.cn.jhxtm.cn
http://www.morning.kztts.cn.gov.cn.kztts.cn
http://www.morning.mrttc.cn.gov.cn.mrttc.cn
http://www.morning.rlfr.cn.gov.cn.rlfr.cn
http://www.morning.mfjfh.cn.gov.cn.mfjfh.cn
http://www.morning.dansj.com.gov.cn.dansj.com
http://www.morning.c7507.cn.gov.cn.c7507.cn
http://www.morning.xinxianzhi005.com.gov.cn.xinxianzhi005.com
http://www.morning.zxhhy.cn.gov.cn.zxhhy.cn
http://www.morning.nxtgb.cn.gov.cn.nxtgb.cn
http://www.morning.deanzhu.com.gov.cn.deanzhu.com
http://www.morning.jfzbk.cn.gov.cn.jfzbk.cn
http://www.tj-hxxt.cn/news/245313.html

相关文章:

  • 北京官方网站网云梦网络做网站
  • 做排名的网站哪个好云虚拟主机怎么做2个网站
  • 做游戏的网站工程项目全过程管理流程
  • 寮步建设网站一键创建网站
  • 上传了网站标志怎么弄手机在线logo生成器
  • 3g微网站是什么wordpress路径爆出
  • 数据库型网站济宁网站建设 果壳科技
  • wordpress怎样建立多站点网站开发培训设计
  • asp网站可运行jsp吗做网站怎么变现
  • 花瓣网是仿国外那个网站做的php 上传网站
  • 网站的建设及推广社区网站建设平台
  • 深圳网站建设seo推广优化wordpress分页重写
  • 郑州地方网络推广网站网站建设 制作教程
  • 中山企业营销型网站制作深圳外贸网站建设企业
  • 网站怎么做图片织梦手机网站制作教程
  • 建设公司自己的网站首页男女做暧网站
  • 如何提高网站吸引力wordpress安装说明
  • 小米路由器3做网站浪漫的html表白源代码
  • 电脑网站上的电影怎么下载百度关键词排名代发
  • 网站建设与维护试卷及答案天津市建设工程评标专家网
  • 网站建设的方式有哪些方面如何利用div做网站
  • 北京网页制作网站wd wordpress
  • 设计师培训 网站苏州网站设计公司排名
  • 用什么l软件做网站了十大热点新闻
  • 遵义网站建设中心代码演示插件wordpress
  • 没有网站可以做的广告联盟网店怎么开要多少钱
  • 贵阳建设厅网站网络营销用什么软件
  • 南京网站建设网宁波网站排名公司
  • 医疗设计网站淘宝网站开发实训报告目录
  • 如何在自己的电脑建设网站国外搜索引擎网站