SCSI子系统基础学习笔记 - 4.scsi_probe

1. 前言

本专题我们开始学习SCSI子系统的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本专题主要以硬件UFS为例,记录SCSI子系统的框架流程。

驱动通过消息传递机制获得的SCSI设备最终都被添加到系统中。实际上,它被挂接在SCSI总线类型(scsi_bus_type),实际代码参考分配SCSI设备描述符时,调用的scsi_alloc_sdev->scsi_sysfs_device_initialize函数。
接下来,要真正用上这些设备,就需要高层驱动,这些高层驱动挂载SCSI总线类型,在被加载时,扫描并认领总线类型上未被绑定的SCSI设备。
SCSI设备属于sdev_class类,Linux SCSI子系统实现了sg(SCSI通用接口)和ses(SCSI磁盘柜服务)接口,注册到这个类下。他们都相当于类特定的高层驱动。

上一节在scsi设备探测时讲到scsi_sysfs_add_sdev(sdev)执行此函数将会添加scsi_device,引发devcice_add(scsi_device->device),而sd_init->scsi_register_driver时会执行driver_add,匹配成功将执行 sd_probe,从此处可以看出每一个探测到的scsi_devei也就是每个逻辑单元LU都会执行一次sd_probe,本文就说明一下sd_probe的执行过程。

kernel版本:5.10
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2. SCSI驱动模型

在这里插入图片描述

  • scsi_device
    给出了作为SCSI设备方面的属性
  • scsi_disk
    给出了作为SCSI磁盘方面的属性,它作为SCSI设备的代表与上层IO关联
  • gendisk
    给出了作为通用磁盘方面的属性
    这三个结构之间相互联系,通过任何一个结构,可以找到另外两个结构。

在SCSI设备被发现(scsi_scan_host),或者SCSI(高层)驱动被加载(init_sd->scsi_register_driver)时,会在SCSI总线类型上和驱动链表中驱动或者设备链表中的设备尝试绑定。在SCSI总线类型(scsi_bus_type,文件drivers/scsi/scsi_sysfs.c)中定义的match回调函数将首先被调用,这个匹配函数就是scsi_bus_match。SCSI总线类型将设备和驱动的匹配留给驱动来做。

如果总线类型的match回调函数匹配成功,接下来会调用总线类型或驱动的probe回调函数。对于SCSI总线没有定义probe回调,因此会调用scsi_driver的probe,也就是sd_probe。

因此,总结SCSI系统的工作模式:
SCSI中层注册SCSI总线类型; SCSI较高层注册SCSI设备驱动;SCSI较低层(SCSI HBA驱动)则负责扫描SCSI总线得到所有的SCSI设备,SCSI设备将和SCSI设备驱动的匹配是根据SCSI INQUIRY响应中的SCSI设备类型域。

3. init_sd

static int __init init_sd(void)
	|--for (i = 0; i < SD_MAJORS; i++)
	|		if (register_blkdev(sd_major(i), "sd") != 0)
	|			continue;
	|		majors++;
	|		blk_register_region(sd_major(i), SD_MINORS, NULL,sd_default_probe, NULL, NULL); 
	|  //注册scsi_disk类
	|--class_register(&sd_disk_class);
	|--sd_cdb_cache = kmem_cache_create("sd_ext_cdb", SD_EXT_CDB_SIZE,0, 0, NULL);
	|--sd_cdb_pool = mempool_create_slab_pool(SD_MEMPOOL_SIZE, sd_cdb_cache);
	|--sd_page_pool = mempool_create_page_pool(SD_MEMPOOL_SIZE, 0);
	|--scsi_register_driver(&sd_template.gendrv);

scsi_register_driver会触发执行driver_register

4. sd_probe

<drivers/scsi/sd.c>
static int sd_probe(struct device *dev)
	|--struct scsi_device *sdp = to_scsi_device(dev);
	|  struct scsi_disk *sdkp;
	|  struct gendisk *gd;
	|--sdkp = kzalloc(sizeof(*sdkp), GFP_KERNEL);
	|--gd = alloc_disk(SD_MINORS);
	|--index = ida_alloc(&sd_index_ida, GFP_KERNEL);
	|  //根据index来确定磁盘名字"sdx"
	|--sd_format_disk_name("sd", index, gd->disk_name, DISK_NAME_LEN); 
	|--sdkp->device = sdp;
    |  sdkp->driver = &sd_template;
    |  sdkp->disk = gd;
    |  sdkp->index = index;
    |  sdkp->max_retries = SD_MAX_RETRIES;
    |  if (!sdp->request_queue->rq_timeout)
    |  		if (sdp->type != TYPE_MOD)
    |  			blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT);
    |  		else
    |			blk_queue_rq_timeout(sdp->request_queue, SD_MOD_TIMEOUT);
    |--device_initialize(&sdkp->dev);
    |--sdkp->dev.parent = dev;
    |--sdkp->dev.class = &sd_disk_class;
    |--dev_set_name(&sdkp->dev, "%s", dev_name(dev));
    |--device_add(&sdkp->dev);
    |--dev_set_drvdata(dev, sdkp);
    |--gd->major = sd_major((index & 0xf0) >> 4);
    |  gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
    |  gd->major = sd_major((index & 0xf0) >> 4);
    |  gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
    |  gd->fops = &sd_fops;
    |  gd->private_data = &sdkp->driver;
    |  gd->queue = sdkp->device->request_queue;
    |    /* defaults, until the device tells us otherwise */
    |--sdp->sector_size = 512;
    |  sdkp->capacity = 0;
    |  sdkp->media_present = 1;
    |  sdkp->write_prot = 0;
    |  sdkp->cache_override = 0;
    |  sdkp->WCE = 0;
    |  sdkp->RCD = 0;
    |  sdkp->ATO = 0;
    |  sdkp->first_scan = 1;
    |  sdkp->max_medium_access_timeouts = SD_MAX_MEDIUM_TIMEOUTS;
    |--sd_revalidate_disk(gd);//第一次调用
    |--gd->flags = GENHD_FL_EXT_DEVT;
    |  if (sdp->removable)
    |  		gd->flags |= GENHD_FL_REMOVABLE;
    |       gd->events |= DISK_EVENT_MEDIA_CHANGE;
    |       gd->event_flags = DISK_EVENT_FLAG_POLL | DISK_EVENT_FLAG_UEVENT;
    |--blk_pm_runtime_init(sdp->request_queue, dev);
    |--device_add_disk(dev, gd, NULL);
    |--sd_revalidate_disk(gd);//第二次调用

经前述init_sd会触发执行scsi driver register,而在设备探测时scsi_scan_host->scsi_probe_and_add_lun->scsi_add_lun->scsi_sysfs_add_sdev时触发执行了scsi device register,因此最终经过match成功后会执行sd_probe。sd_probe是SCSI磁盘驱动的实际入口,它在驱动初始化期间,以及有新的SCSI设备被挂载到系统中时(scsi_device注册到scsi bus时)被调用。每个出现的SCSI设备都会调用一次,它在给定的<host,channel,id,lun>和新的设备名(如/dev/sda)之间建立映射。更准确的说,在这里选定了块设备的主设备号和次设备号。唯一的参数是scsi_device的内嵌通用设备sdev_gendev,因为SCSI设备是通过内嵌通用设备链入SCSI总线类型的设备链表的。

  1. 过滤掉本驱动不能处理的非磁盘设备,因为总线把权利交给驱动,因此有可能传入的不是SCSI设备,需要过滤掉

  2. sdkp = kzalloc(sizeof(*sdkp), GFP_KERNEL);
    分配scsi_disk描述符,参考通用block基础学习笔记 - 2. 添加磁盘到系统

  3. gd = alloc_disk(SD_MINORS);
    分配gendisk,需要处理分区,对于SCSI磁盘可以固定有15个分区,加上作为整体使用的分区有16个

  4. error = ida_get_new(&sd_index_ida, &index);
    error = sd_format_disk_name(“sd”, index, gd->disk_name, DISK_NAME_LEN);
    为SCSI磁盘分配设备名,设备名为sda-sdz,sdaa-sdzz, sdaaa-sdzzz
    关于主次设备号的生成,TODO

  5. 构建scsi_device, scsi_disk, gendisk的关系
    在这里插入图片描述

  6. blk_queue_rq_timeout
    设置请求队列超时时间

  7. device_initialize(&sdkp->dev);
    初始化scsi磁盘内嵌device
    device_add(&sdkp->dev)
    为scsi磁盘在sysfs中创建目录

  8. 对通用磁盘描述符gendisk进行一些初始化

  9. 找到通用磁盘描述符指针和SCSI设备描述符指针,对SCSI设备描述符、SCSI磁盘描述符进行初始化,设置为默认值,随后会通过命令从SCSI设备获取信息修改

  10. sd_revalidate_disk(gd); 该函数的第一次调用,在device_add_disk之前进行
    (1)sd_spinup_disk(sdkp);向scsi磁盘发送命令,让磁盘转动起来
    (2)sd_read_capacity(sdkp, buffer); 读取磁盘容量
    (3)sd_read_block_limits(sdkp);
    sd_read_block_characteristics(sdkp);
    (4)sd_read_write_protect_flag(sdkp, buffer);读取磁盘写保护标志
    (5)sd_read_cache_type(sdkp, buffer);读取磁盘缓存类型
    (6)sd_read_app_tag_own(sdkp, buffer)读取磁盘ATO位
    (7)sdkp->first_scan = 0;此标志是判断是否函数第一次调用
    (8)blk_queue_ordered(sdkp->disk->queue, ordered, sd_prepare_flush);
    根据SCSI磁盘特性确定IO的屏障能力
    (9)set_capacity(disk, sdkp->capacity);根据INQUERY命令查询到的磁盘容量设置

  11. blk_queue_prep_rq(sdp->request_queue, sd_prep_fn)
    将scsi请求队列的prep_rq_fn设置为sd_prep_fn
    此后不仅可以处理来自scsi层的请求,也可以处理来自通用io层的请求,可以为bio请求构建scsi命令

  12. gd->driverfs_dev = &sdp->sdev_gendev;
    联系通用磁盘和scsi设备,每个SCSI device对应一个通用磁盘

  13. dev_set_drvdata(&sdp->sdev_gendev, sdkp);
    将scsi磁盘描述符指针赋值给sdp->sdev_gendev->drv_data域,sdp为scsi_device类型指针
    scsi磁盘驱动的其它回调函数(如sd_rescan, sd_shutdown等)都需要拿到scsi磁盘描述符

  14. device_add_disk:将通用磁盘添加到系统
    注:add_disk是对device_add_disk的封装,参考通用block基础学习笔记 - 2. 添加磁盘到系统

  15. sd_revalidate_disk(gd); 第二次调用,在add_disk之后

参考文档

存储技术原理分析

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值