天天看點

深入淺出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主機擴充卡的公有部分和私有部分

深入淺出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子系統(四)添加擴充卡到系統添加擴充卡到系統

 主機擴充卡子目錄樹下包含主機擴充卡自身的屬性檔案,以及目标節點子樹和更下級的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檔案系統為該主機擴充卡建立目錄項。

繼續閱讀