本文以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函数。