深入浅出SCSI子系统(四)添加适配器到系统

2023-12-28 10:09

本文主要是介绍深入浅出SCSI子系统(四)添加适配器到系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

添加适配器到系统

scsi_host_alloc()

scsi_add_host()


添加适配器到系统

SCSI低层驱动是面向主机适配器的,低层驱动被加载时,首先要添加主机适配器。主机适配器可以在PCI子系统完成ID匹配时添加,或者通过手工方式添加。所有基于硬件PCI接口的主机适配器都采用前一种方式,而UNH iSCSI启动器采用的是后一种方式。

添加主机适配器包括两部分的内容,为主机适配器分配数据结构,将主机适配器添加到系统。SCSI中间层为此提供了两个公共函数:scsi_host_alloc和scsi_add_host。

在低层驱动,主机适配器(控制器的驱动代码中调用,下面以x86 pm8001控制器驱动为例子)

kernel\drivers\scsi\pm8001\pm8001_init.c


static struct scsi_host_template pm8001_sht = {.module			= THIS_MODULE,.name			= DRV_NAME,.queuecommand		= sas_queuecommand,.target_alloc		= sas_target_alloc,.slave_configure	= sas_slave_configure,.scan_finished		= pm8001_scan_finished,.scan_start		= pm8001_scan_start,.change_queue_depth	= sas_change_queue_depth,.bios_param		= sas_bios_param,.can_queue		= 1,.this_id		= -1,.sg_tablesize		= SG_ALL,.max_sectors		= SCSI_DEFAULT_MAX_SECTORS,.use_clustering		= ENABLE_CLUSTERING,.eh_device_reset_handler = sas_eh_device_reset_handler,.eh_bus_reset_handler	= sas_eh_bus_reset_handler,.target_destroy		= sas_target_destroy,.ioctl			= sas_ioctl,.shost_attrs		= pm8001_host_attrs,.track_queue_depth	= 1,
};static int pm8001_pci_probe(struct pci_dev *pdev,const struct pci_device_id *ent)
{struct Scsi_Host *shost = NULL;shost = scsi_host_alloc(&pm8001_sht, sizeof(void *));}

scsi_host_alloc()

下面介绍函数scsi_host_alloc()代码(摘自文件drivers/scsi/hosts.c)

struct Scsi_Host {struct list_head	__devices;struct list_head	__targets;	struct scsi_host_cmd_pool *cmd_pool;spinlock_t		free_list_lock;struct list_head	free_list; /* backup store of cmd structs */struct list_head	starved_list;spinlock_t		default_lock;spinlock_t		*host_lock;struct mutex		scan_mutex;/* serialize scanning activity */struct list_head	eh_cmd_q;struct task_struct    * ehandler;  /* Error recovery thread. */struct completion     * eh_action; /* Wait for specific actions on the					      host. */wait_queue_head_t       host_wait;struct scsi_host_template *hostt;struct scsi_transport_template *transportt;union {struct blk_queue_tag	*bqt;struct blk_mq_tag_set	tag_set;};atomic_t host_busy;		   /* commands actually active on low-level */atomic_t host_blocked;unsigned int host_failed;	   /* commands that failed.					      protected by host_lock */unsigned int host_eh_scheduled;    /* EH scheduled without command */unsigned int host_no;  /* Used for IOCTL_GET_IDLUN, /proc/scsi et al. */unsigned long last_reset;unsigned int max_channel;unsigned int max_id;u64 max_lun;unsigned int unique_id;unsigned short max_cmd_len;int this_id;int can_queue;short cmd_per_lun;short unsigned int sg_tablesize;short unsigned int sg_prot_tablesize;unsigned int max_sectors;unsigned long dma_boundary;unsigned nr_hw_queues;unsigned long cmd_serial_number;unsigned active_mode:2;unsigned unchecked_isa_dma:1;unsigned use_clustering:1;unsigned host_self_blocked:1;unsigned reverse_ordering:1;unsigned tmf_in_progress:1;unsigned async_scan:1;unsigned eh_noresume:1;unsigned no_write_same:1;unsigned use_blk_mq:1;unsigned use_cmd_list:1;unsigned short_inquiry:1;struct workqueue_struct *work_q;struct workqueue_struct *tmf_work_q;unsigned no_scsi2_lun_in_cdb:1;unsigned int max_host_blocked;unsigned int prot_capabilities;unsigned char prot_guard_type;unsigned long base;unsigned long io_port;unsigned char n_io_port;unsigned char dma_channel;unsigned int  irq;enum scsi_host_state shost_state;struct device		shost_gendev, shost_dev;struct list_head sht_legacy_list;void *shost_data;struct device *dma_dev;unsigned long hostdata[0]  /* Used for storage of host specific stuff */__attribute__ ((aligned (sizeof(unsigned long))));
};

scsi_host_alloc函数分配一个新的Scsi_Host描述符并进行基本的初始化。第一个参数为指向SCSI主机适配器模板的指针;第二个参数为驱动私有数据分配的额外字节数。返回值为指向Scsi_Host描述符的指针;或者在失败时返回NULL。一次性分配SCSI主机适配器的公有部分和私有部分的情况如下图:

                                       一次性分配SCSI主机适配器的公有部分和私有部分

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{struct Scsi_Host *shost;gfp_t gfp_mask = GFP_KERNEL;shost = kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);if (!shost)return NULL;
}

Scsi_Host的空间一次性分配。使用kzalloc (GFP_KERNEL)

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{shost->host_no = atomic_inc_return(&scsi_host_next_hn) - 1;
}

Scsi_Host是一个庞大的数据结构,它的初始化也比较烦琐,所幸大多数域都可以根据“主机适配器模板”赋值,或者先赋一个默认的值,等待以后再覆写。需要特别说明的是host_no域,即主机适配器编号。主机适配器是在系统范围内编号的,或者说,系统中的主机适配器统一进行编号。在Linux内核中有一个全局变量scsi_host_next_hn,表示下一个新的主机适配器的编号。它的初值为零,每次发现一个新的主机适配器,将全局变量scsi_host_next_hn的值赋给host_no域,然后再递增。

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{/* These three are default values which can be overridden */shost->max_channel = 0;shost->max_id = 8;shost->max_lun = 8;
}

将max_channel初始化为0,将max_id和max_lun初始化为8,这些域都被先赋予一些默认的值,在本函数返回后,低层驱动可以对这些域进行覆写。

我们关注sysfs文件系统有关的代码。先把主机适配器的子目录树画在图:

                                                                SCSI总线的sysfs

 主机适配器子目录树下包含主机适配器自身的属性文件,以及目标节点子树和更下级的SCSI设备子树。

标记①为主机适配器目录名host#,其中#为主机适配器编号;

标记②为目标节点目录名target#:#:#,其中#:#:#分别为主机适配器编号、通道编号和ID编号;

标记③为SCSI设备目录名#:#:#:#,其中#:#:#分别为主机适配器编号、通道编号、ID编号,以及逻辑单元编号。

struct Scsi_Host {struct device		shost_gendev, shost_dev;
}

主机适配器属于shost_class类(类名为scsi_host),为了和类关联起来,在左下部分看到有同名的子目录树。因此,每个主机适配器定义了两个内嵌设备描述符,分别和这两个灰色标记的目录对应。对应上层host#目录的内嵌设备描述符为shost_gendev域,被称为内嵌通用设备;对应下层host#目录的内嵌设备描述符为shost_dev域,被称为内嵌类设备。

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{//初始化内嵌通用设备device_initialize(&shost->shost_gendev);dev_set_name(&shost->shost_gendev, "host%d", shost->host_no);shost->shost_gendev.bus = &scsi_bus_type;shost->shost_gendev.type = &scsi_host_type;//初始化内嵌类设备device_initialize(&shost->shost_dev);shost->shost_dev.parent = &shost->shost_gendev;shost->shost_dev.class = &shost_class;dev_set_name(&shost->shost_dev, "host%d", shost->host_no);shost->shost_dev.groups = scsi_sysfs_shost_attr_groups;
}

初始化内嵌通用设备,初始化内嵌类设备。它们之间是相互关联的,内嵌通用设备为内嵌类设备的父设备shost->shost_dev.parent = &shost->shost_gendev。

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{shost->ehandler = kthread_run(scsi_error_handler, shost,"scsi_eh_%d", shost->host_no);
}

在Linux系统中,每个主机适配器都有一个SCSI错误恢复内核线程。启动之,线程名为scsi_eh_#,其中#为主机适配器编号,线程执行的主体函数为scsi_error_handler。关于更详细的描述,参见后面的SCSI错误恢复。

struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{scsi_proc_hostdir_add(shost->hostt);
}

调用scsi_proc_hostdir_add,在proc文件系统中为这个主机适配器添加一个目录

scsi_add_host()

下面介绍函数scsi_add_host()代码(摘自文件include/scsi/scsi_host.h)

static inline int __must_check scsi_add_host(struct Scsi_Host *host,struct device *dev)
{return scsi_add_host_with_dma(host, dev, dev);
}

scsi_add_host函数有两个参数。第一个为指向主机适配器描述符的指针,第二个指向这个SCSI主机适配器在驱动模型中的父设备的device描述符,它决定了这个SCSI主机适配器在sysfs文件系统中的位置,可以为NULL。但对于PCI-SCSI驱动,通常将它设置为对应PCI设备的内嵌驱动模型设备。例如某个SCSI主机适配器对应sysfs文件系统的/sys/devices/pci0000:00/0000:00:07.1/host0/目录。成功返回0,错误返回非0值。

函数scsi_add_host_with_dma()代码(摘自文件drivers/scsi/hosts.c)

int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,struct device *dma_dev)
{error = scsi_setup_command_freelist(shost);
}

在调用scsi_add_host_with_dma时增加了一个参数:主机适配器的DMA设备,它只是用于虚拟主机适配器环境下,在我们这里不需要给予太多关注。对于PCI-SCSI设备,传入的就是对应PCI设备的内嵌驱动模型设备。

scsi_setup_command_freelist函数为主机适配器准备用于分配SCSI命令结构和感测缓冲区的存储池结构,以及准备SCSI命令结构的后备仓库链表。

int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,struct device *dma_dev)
{if (!shost->shost_gendev.parent)shost->shost_gendev.parent = dev ? dev : &platform_bus;
}

确定主机适配器在sysfs中的位置。我们说过,一般情况下,主机适配器会被放在引出它的PCI设备之下,主机适配器驱动一般在调用scsi_add_host函数时会传入对应的pci_dev描述符中的内嵌device地址,在这里赋值给主机适配器内嵌通用设备的parent域。当然,如果传入的dev参数为NULL,我们就把它放在sys/devices/platform/目录下,platform设备是某些特定平台专有的外围设备。

int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,struct device *dma_dev)
{error = device_add(&shost->shost_gendev);error = device_add(&shost->shost_dev);error = scsi_sysfs_add_host(shost);
}

主机适配器的内嵌通用设备和内嵌类设备的初始化已经全部完成。将SCSI子系统的驱动模型设备关系整理如上图所示,为方便理解,其中还包含了后面才会添加的目标节点和SCSI设备。device_add(&shost->shost_gendev);将主机适配器的内嵌通用设备添加到系统中。device_add(&shost->shost_dev);将主机适配器的内嵌类设备添加到系统中。scsi_sysfs_add_host(shost)调用scsi_sysfs_add_host将主机适配器添加到子系统,包括为主机适配器属性添加对应的文件。

int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,struct device *dma_dev)
{scsi_proc_host_add(shost);
}

用于proc文件系统为该主机适配器创建目录项。

这篇关于深入浅出SCSI子系统(四)添加适配器到系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/545603

相关文章

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux查询服务器系统版本号的多种方法

《Linux查询服务器系统版本号的多种方法》在Linux系统管理和维护工作中,了解当前操作系统的版本信息是最基础也是最重要的操作之一,系统版本不仅关系到软件兼容性、安全更新策略,还直接影响到故障排查和... 目录一、引言:系统版本查询的重要性二、基础命令解析:cat /etc/Centos-release详

更改linux系统的默认Python版本方式

《更改linux系统的默认Python版本方式》通过删除原Python软链接并创建指向python3.6的新链接,可切换系统默认Python版本,需注意版本冲突、环境混乱及维护问题,建议使用pyenv... 目录更改系统的默认python版本软链接软链接的特点创建软链接的命令使用场景注意事项总结更改系统的默

在Linux系统上连接GitHub的方法步骤(适用2025年)

《在Linux系统上连接GitHub的方法步骤(适用2025年)》在2025年,使用Linux系统连接GitHub的推荐方式是通过SSH(SecureShell)协议进行身份验证,这种方式不仅安全,还... 目录步骤一:检查并安装 Git步骤二:生成 SSH 密钥步骤三:将 SSH 公钥添加到 github

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻