Linux SCSI 子系统 III

本文以LSI公司基于PCIe接口的MegaSAS RAID卡驱动为例,分析SCSI总线在内核注册对象的过程。SCSI设备在内核初始化可以分为两部分,LLDD的部分和HLDD的部分,但无论哪部分,和其他内核对象的构造流程一样,都遵循”分配->初始化->注册“的流程。

LLDD INIT

LLDD的init主要是在内核中构造与SCSI设备直接相关的对象。在主板上已经插入了RAID卡的前提下,内核在启动过程中基于相关协议标准获取PCIe设备信息,并根据这些信息在内核中构造相应的PCIe设备对象,此外,内核在启动过程还会加载该RAID卡的驱动,本质上这是一个PCIe驱动,无论PCIe设备对象和PCIe驱动谁先谁后,只要二者都被加载进内核,就会match并回调PCI驱动的probe()。对于该款RAID卡的驱动来说,probe()被注册为megasas_probe_one(),由该函数根据板卡需求在内核SCSI子系统中构造相关对象。
梳理mega_probe_one()的流程,可以发现,该函数依然遵循内核驱动框架的”分配->初始化->注册“的流程, 其中,又分为针对SCSI适配器对象的三步走( scsi_host_alloc()scsi_add_host())以及针对SCSI设备的三步走(scsi_scan_host())。

megasas_probe_one()
	scsi_host_alloc()               //alloc scsi host
		init Scsi_Host shost        //init scsi host
		scsi_proc_hostdir_add()     
	megasas_io_attach() 
		scsi_add_host()             //register scsi host
			scsi_add_host_with_dma()
	scsi_scan_host()                //alloc, init and register scsi device
		scsi_prep_async_scan()
		do_scsi_scan_host()
		async_schedule(do_scan_async)

scsi_host_alloc()
主要负责Scsi_Host对象的分配和部分域的缺省值的初始化,相关逻辑整理如下:

struct Scsi_Host * scsi_host_alloc(struct scsi_host_template *sht, int privsize)               //alloc scsi host
{ 
	Scsi_Host shost = kzalloc();
	shost->shost_state = SHOST_CREATED;
	init_waitqueue_head(&shost->host_wait);
	//init list_head
	INIT_LIST_HEAD(list_heads in shost); 
	//set default values which can be overwritten
	shost->max_channel = 0;
	shost->max_id = 8;
	shost->max_lun = 8;
	shost->transportt = &blank_transport_template;
    
	//init Scsi_Host by scsi_host_template
	shost->hostt = sht;
	shost->this_id = sht->this_id;
	
	//init DD fields
	device_initialize(&shost->shost_gendev);
	shost->shost_gendev.bus = &scsi_bus_type;
	shost->shost_gendev.type = &scsi_host_type;
	device_initialize(&shost->shost_dev);
	shost->ehandler = kthread_run(scsi_error_handler);
	shost->tmf_work_q = alloc_workqueue();
	scsi_proc_hostdir_add() 
}

scsi_add_host()
负责将已经分配好并部分初始化的Scsi_Host对象继续初始化并注册到内核SCSI子系统中。该函数的实质是调用scsi_add_host_with_dma函数。

static inline int __must_check scsi_add_host(struct Scsi_Host *host, struct device *dev)
{
	return scsi_add_host_with_dma(host, dev, dev);
}
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;
	device_add(&shost->shost_gendev);
	pm_runtime_set_active(&shost->shost_gendev);
	pm_runtime_enable(&shost->shost_gendev);
	device_enable_async_suspend(&shost->shost_gendev);

	scsi_host_set_state(shost, SHOST_RUNNING);

	device_enable_async_suspend(&shost->shost_dev);
	device_add(&shost->shost_dev);
	
	scsi_sysfs_add_host(shost);
	scsi_proc_host_add(shost);
}

scsi_scan_host()
可以分为两部分,同步执行的部分和异步执行的部分。相关调用关系整理如下。可以看出,对于SCSI总线的扫描,按照scsi_scan_host_selected()->scsi_scan_channel()->__scsi_scan_target()->scsi_probe_and_add_lun()->scsi_add_lun()的流程向内核中添加SCSI总线上的设备相应的设备对象。

scsi_scan_host()                    //同步执行
	scsi_prep_async_scan()
	do_scsi_scan_host()
	async_schedule(do_scan_async)   //调度do_scan_async作为异步执行部分
do_scan_async()
	do_scsi_scan_host()
		shost->hostt->scan_start()//如果驱动需要执行特定的扫描逻辑,就要执行这两个函数, megasas没注册
		shost->hostt->scan_finished()
		scsi_scan_host_selected()
			scsi_host_scan_allowed()
			scsi_autopm_get_host()
			scsi_scan_channel()
 				__scsi_scan_target()
					dev_to_shost()      //驱动中的trick之一,
					scsi_alloc_target()
					scsi_probe_and_add_lun()
						scsi_device_lookup_by_target()
							scsi_device_lookup_by_target()
								list_for_each_entry()
						scsi_alloc_sdev()
						scsi_probe_lun()
						scsi_add_lun()                  //添加lun
	scsi_finish_async_scan()

HLDD Init

LLDD初始化过程为在SCSI总线上探测到的LUN构造相应的scsi_device对象并注册到内核,即注册到scsi_bus_type管理的总线中,根据SubSySScSi – RoLES一文讨论的,如此,就会match到相应的SCSI设备驱动,执行其probe函数——sd_probe(),HLDD的初始化即由此开始,和scsi_scan_host()一样,sd_probe也分为同步执行的部分和异步执行部分,但总的来说,HLDD初始化主要工作是分配、初始化并注册供Block层使用的sd_disk,gendisk等关键对象
sd_probe()
的调用树梳理如下:

sd_probe()
    struct scsi_disk sdkp
    device_initialize()
    device_add(sdkp)
    dev_set_drvdata()
    async_schedule_domain(sd_probe_async)
sd_probe_async()
	init sdp sdkp
	gendisk gd=sdkp->disk
	init gd 
	add_disk(gd)
		bdi_register_dev()
		blk_register_region()
		register_disk()
		blk_register_queue()
		disk_add_events()
		blk_integrity_add()
	sd_revalidate_disk(gd)
		sd_spinup_disk()            //转动磁盘
			scsi_execute_req()
		sd_readcapacity()
		set_capacity(disk)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.