因为本文讨论的大部分都是scsi模块的内容,所以将名字更改,但是这是之前两篇文章的继续, libata是scsi的子系统。
scsi部分主要是重要的结构的建立, 启动层上其下的作用。
linux/driver/scsi/sd.c
这个模块初始化早于sata驱动的初始化,我们看一下该模块的初始化函数。
/**
* init_sd - entry point for this driver (both when built in or when
* a module).
*
* Note: this function registers this driver with the scsi mid-level.
**/
static int __init init_sd(void)
{
int majors = 0, i, err;
SCSI_LOG_HLQUEUE(3, printk("init_sd: sd driver entry point\n"));
for (i = 0; i < SD_MAJORS; i++)
if (register_blkdev(sd_major(i), "sd") == 0)
majors++;
if (!majors)
return -ENODEV;
err = class_register(&sd_disk_class);
if (err)
goto err_out;
err = scsi_register_driver(&sd_template.gendrv);
if (err)
goto err_out_class;
return 0;
err_out_class:
class_unregister(&sd_disk_class);
err_out:
for (i = 0; i < SD_MAJORS; i++)
unregister_blkdev(sd_major(i), "sd");
return err;
}
static struct scsi_driver sd_template = {
.owner = THIS_MODULE,
.gendrv = {
.name = "sd",
.probe = sd_probe,
.remove = sd_remove,
.suspend = sd_suspend,
.resume = sd_resume,
.shutdown = sd_shutdown,
},
.rescan = sd_rescan,
.done = sd_done,
};
一般我们认为系统在执行完scs_register_driver系统会根据总线上的设备情况,执行进一步的处理 , 如执行驱动提供的probe函数,但是因为这个模块早于sata驱动的初始化,系统没有其他的硬盘设备,导致这里并没有在总线上发现设备。知道sata初始化完并向scsi层注册设备后,才会手动执行这里probe函数,进而完成sd模块的初始化,如分配设备号等。
下面我们继续分析之前两篇文章剩下的一点部分,引入今天我们要分析的内容。
之前我们看到(第一篇文章)系统在执行到ata_host_register会等待一个内核线程的执行,并在第二篇文章看到了内核线程的执行流程以及该线程结束后会唤醒等待的主初始化线程,现在我们从等待后面继续往下看。
ata_host_register函数剩下的部分比较简单,只有几行代码,我们主要看他调用的一个函数,如下:
linux/driver/ata/libata_scsi.c
void ata_scsi_scan_host(struct ata_port *ap, int sync)
{
int tries = 5;
struct ata_device *last_failed_dev = NULL;
struct ata_link *link;
struct ata_device *dev;
if (ap->flags & ATA_FLAG_DISABLED)
return;
repeat:
ata_port_for_each_link(link, ap) {
ata_link_for_each_dev(dev, link) {
struct scsi_device *sdev;
int channel = 0, id = 0;
if (!ata_dev_enabled(dev) || dev->sdev)
continue;
if (ata_is_host_link(link))
id = dev->devno;
else
channel = link->pmp;
sdev = __scsi_add_device(ap->scsi_host, channel, id, 0,
NULL);
if (!IS_ERR(sdev)) {
dev->sdev = sdev;
scsi_device_put(sdev);
}
}
}
/* If we scanned while EH was in progress or allocation
* failure occurred, scan would have failed silently. Check
* whether all devices are attached.
*/
ata_port_for_each_link(link, ap) {
ata_link_for_each_dev(dev, link) {
if (ata_dev_enabled(dev) && !dev->sdev)
goto exit_loop;
}
}
exit_loop:
if (!link)
return;
/* we're missing some SCSI devices */
if (sync) {
/* If caller requested synchrnous scan && we've made
* any progress, sleep briefly and repeat.
*/
if (dev != last_failed_dev) {
msleep(100);
last_failed_dev = dev;
goto repeat;
}
/* We might be failing to detect boot device, give it
* a few more chances.
*/
if (--tries) {
msleep(100);
goto repeat;
}
ata_port_printk(ap, KERN_ERR, "WARNING: synchronous SCSI scan "
"failed without making any progress,\n"
" switching to async\n");
}
queue_delayed_work(ata_aux_wq, &ap->hotplug_task,
round_jiffies_relative(HZ));
}
linux/dirver/scsi/scsi_scan.c
struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel,
uint id, uint lun, void *hostdata)
{
struct scsi_device *sdev = ERR_PTR(-ENODEV);
struct device *parent = &shost->shost_gendev;
struct scsi_target *starget;
if (strncmp(scsi_scan_type, "none", 4) == 0)
return ERR_PTR(-ENODEV);
starget = scsi_alloc_target(parent, channel, id);
if (!starget)
return ERR_PTR(-ENOMEM);
mutex_lock(&shost->scan_mutex);
if (!shost->async_scan)
scsi_complete_async_scans();
if (scsi_host_scan_allowed(shost))
scsi_probe_and_add_lun(starget, lun, NULL, &sdev, 1, hostdata);
mutex_unlock(&shost->scan_mutex);
scsi_target_reap(starget);
put_device(&starget->dev);
return sdev;
}
/**
* scsi_probe_and_add_lun - probe a LUN, if a LUN is found add it
* @starget: pointer to target device structure
* @lun: LUN of target device
* @sdevscan: probe the LUN corresponding to this scsi_device
* @sdevnew: store the value of any new scsi_device allocated
* @bflagsp: store bflags here if not NULL
*
* Description:
* Call scsi_probe_lun, if a LUN with an attached device is found,
* allocate and set it up by calling scsi_add_lun.
*
* Return:
* SCSI_SCAN_NO_RESPONSE: could not allocate or setup a scsi_device
* SCSI_SCAN_TARGET_PRESENT: target responded, but no device is
* attached at the LUN
* SCSI_SCAN_LUN_PRESENT: a new scsi_device was allocated and initialized
**/
static int scsi_probe_and_add_lun(struct scsi_target *starget,
uint lun, int *bflagsp,
struct scsi_device **sdevp, int rescan,
void *hostdata)
{
struct scsi_device *sdev;
unsigned char *result;
int bflags, res = SCSI_SCAN_NO_RESPONSE, result_len = 256;
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
/*
* The rescan flag is used as an optimization, the first scan of a
* host adapter calls into here with rescan == 0.
*/
sdev = scsi_device_lookup_by_target(starget, lun);
if (sdev) {
if (rescan || sdev->sdev_state != SDEV_CREATED) {
SCSI_LOG_SCAN_BUS(3, printk(KERN_INFO
"scsi scan: device exists on %s\n",
sdev->sdev_gendev.bus_id));
if (sdevp)
*sdevp = sdev;
else
scsi_device_put(sdev);
if (bflagsp)
*bflagsp = scsi_get_device_flags(sdev,
sdev->vendor,
sdev->model);
return SCSI_SCAN_LUN_PRESENT;
}
scsi_device_put(sdev);
} else
sdev = scsi_alloc_sdev(starget, lun, hostdata);
if (!sdev)
goto out;
result = kmalloc(result_len, GFP_ATOMIC |
((shost->unchecked_isa_dma) ? __GFP_DMA : 0));
if (!result)
goto out_free_sdev;
if (scsi_probe_lun(sdev, result, result_len, &bflags))
goto out_free_result;
if (bflagsp)
*bflagsp = bflags;
/*
* result contains valid SCSI INQUIRY data.
*/
if (((result[0] >> 5) == 3) && !(bflags & BLIST_ATTACH_PQ3)) {
/*
* For a Peripheral qualifier 3 (011b), the SCSI
* spec says: The device server is not capable of
* supporting a physical device on this logical
* unit.
*
* For disks, this implies that there is no
* logical disk configured at sdev->lun, but there
* is a target id responding.
*/
SCSI_LOG_SCAN_BUS(2, sdev_printk(KERN_INFO, sdev, "scsi scan:"
" peripheral qualifier of 3, device not"
" added\n"))
if (lun == 0) {
SCSI_LOG_SCAN_BUS(1, {
unsigned char vend[9];
unsigned char mod[17];
sdev_printk(KERN_INFO, sdev,
"scsi scan: consider passing scsi_mod."
"dev_flags=%s:%s:0x240 or 0x1000240\n",
scsi_inq_str(vend, result, 8, 16),
scsi_inq_str(mod, result, 16, 32));
});
}
res = SCSI_SCAN_TARGET_PRESENT;
goto out_free_result;
}
/*
* Some targets may set slight variations of PQ and PDT to signal
* that no LUN is present, so don't add sdev in these cases.
* Two specific examples are:
* 1) NetApp targets: return PQ=1, PDT=0x1f
* 2) USB UFI: returns PDT=0x1f, with the PQ bits being "reserved"
* in the UFI 1.0 spec (we cannot rely on reserved bits).
*
* References:
* 1) SCSI SPC-3, pp. 145-146
* PQ=1: "A peripheral device having the specified peripheral
* device type is not connected to this logical unit. However, the
* device server is capable of supporting the specified peripheral
* device type on this logical unit."
* PDT=0x1f: "Unknown or no device type"
* 2) USB UFI 1.0, p. 20
* PDT=00h Direct-access device (floppy)
* PDT=1Fh none (no FDD connected to the requested logical unit)
*/
if (((result[0] >> 5) == 1 || starget->pdt_1f_for_no_lun) &&
(result[0] & 0x1f) == 0x1f) {
SCSI_LOG_SCAN_BUS(3, printk(KERN_INFO
"scsi scan: peripheral device type"
" of 31, no device added\n"));
res = SCSI_SCAN_TARGET_PRESENT;
goto out_free_result;
}
res = scsi_add_lun(sdev, result, &bflags, shost->async_scan);
if (res == SCSI_SCAN_LUN_PRESENT) {
if (bflags & BLIST_KEY) {
sdev->lockable = 0;
scsi_unlock_floptical(sdev, result);
}
}
out_free_result:
kfree(result);
out_free_sdev:
if (res == SCSI_SCAN_LUN_PRESENT) {
if (sdevp) {
if (scsi_device_get(sdev) == 0) {
*sdevp = sdev;
} else {
__scsi_remove_device(sdev);
res = SCSI_SCAN_NO_RESPONSE;
}
}
} else
scsi_destroy_sdev(sdev);
out:
return res;
}
/**
* scsi_add_lun - allocate and fully initialze a scsi_device
* @sdev: holds information to be stored in the new scsi_device
* @inq_result: holds the result of a previous INQUIRY to the LUN
* @bflags: black/white list flag
* @async: 1 if this device is being scanned asynchronously
*
* Description:
* Initialize the scsi_device @sdev. Optionally set fields based
* on values in *@bflags.
*
* Return:
* SCSI_SCAN_NO_RESPONSE: could not allocate or setup a scsi_device
* SCSI_SCAN_LUN_PRESENT: a new scsi_device was allocated and initialized
**/
static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
int *bflags, int async)
{
/*
* XXX do not save the inquiry, since it can change underneath us,
* save just vendor/model/rev.
*
* Rather than save it and have an ioctl that retrieves the saved
* value, have an ioctl that executes the same INQUIRY code used
* in scsi_probe_lun, let user level programs doing INQUIRY
* scanning run at their own risk, or supply a user level program
* that can correctly scan.
*/
/*
* Copy at least 36 bytes of INQUIRY data, so that we don't
* dereference unallocated memory when accessing the Vendor,
* Product, and Revision strings. Badly behaved devices may set
* the INQUIRY Additional Length byte to a small value, indicating
* these strings are invalid, but often they contain plausible data
* nonetheless. It doesn't matter if the device sent < 36 bytes
* total, since scsi_probe_lun() initializes inq_result with 0s.
*/
sdev->inquiry = kmemdup(inq_result,
max_t(size_t, sdev->inquiry_len, 36),
GFP_ATOMIC);
if (sdev->inquiry == NULL)
return SCSI_SCAN_NO_RESPONSE;
sdev->vendor = (char *) (sdev->inquiry + 8);
sdev->model = (char *) (sdev->inquiry + 16);
sdev->rev = (char *) (sdev->inquiry + 32);
if (*bflags & BLIST_ISROM) {
sdev->type = TYPE_ROM;
sdev->removable = 1;
} else {
sdev->type = (inq_result[0] & 0x1f);
sdev->removable = (inq_result[1] & 0x80) >> 7;
}
switch (sdev->type) {
case TYPE_RBC:
case TYPE_TAPE:
case TYPE_DISK:
case TYPE_PRINTER:
case TYPE_MOD:
case TYPE_PROCESSOR:
case TYPE_SCANNER:
case TYPE_MEDIUM_CHANGER:
case TYPE_ENCLOSURE:
case TYPE_COMM:
case TYPE_RAID:
sdev->writeable = 1;
break;
case TYPE_ROM:
case TYPE_WORM:
sdev->writeable = 0;
break;
default:
printk(KERN_INFO "scsi: unknown device type %d\n", sdev->type);
}
if (sdev->type == TYPE_RBC || sdev->type == TYPE_ROM) {
/* RBC and MMC devices can return SCSI-3 compliance and yet
* still not support REPORT LUNS, so make them act as
* BLIST_NOREPORTLUN unless BLIST_REPORTLUN2 is
* specifically set */
if ((*bflags & BLIST_REPORTLUN2) == 0)
*bflags |= BLIST_NOREPORTLUN;
}
/*
* For a peripheral qualifier (PQ) value of 1 (001b), the SCSI
* spec says: The device server is capable of supporting the
* specified peripheral device type on this logical unit. However,
* the physical device is not currently connected to this logical
* unit.
*
* The above is vague, as it implies that we could treat 001 and
* 011 the same. Stay compatible with previous code, and create a
* scsi_device for a PQ of 1
*
* Don't set the device offline here; rather let the upper
* level drivers eval the PQ to decide whether they should
* attach. So remove ((inq_result[0] >> 5) & 7) == 1 check.
*/
sdev->inq_periph_qual = (inq_result[0] >> 5) & 7;
sdev->lockable = sdev->removable;
sdev->soft_reset = (inq_result[7] & 1) && ((inq_result[3] & 7) == 2);
if (sdev->scsi_level >= SCSI_3 ||
(sdev->inquiry_len > 56 && inq_result[56] & 0x04))
sdev->ppr = 1;
if (inq_result[7] & 0x60)
sdev->wdtr = 1;
if (inq_result[7] & 0x10)
sdev->sdtr = 1;
sdev_printk(KERN_NOTICE, sdev, "%s %.8s %.16s %.4s PQ: %d "
"ANSI: %d%s\n", scsi_device_type(sdev->type),
sdev->vendor, sdev->model, sdev->rev,
sdev->inq_periph_qual, inq_result[2] & 0x07,
(inq_result[3] & 0x0f) == 1 ? " CCS" : "");
if ((sdev->scsi_level >= SCSI_2) && (inq_result[7] & 2) &&
!(*bflags & BLIST_NOTQ))
sdev->tagged_supported = 1;
/*
* Some devices (Texel CD ROM drives) have handshaking problems
* when used with the Seagate controllers. borken is initialized
* to 1, and then set it to 0 here.
*/
if ((*bflags & BLIST_BORKEN) == 0)
sdev->borken = 0;
if (*bflags & BLIST_NO_ULD_ATTACH)
sdev->no_uld_attach = 1;
/*
* Apparently some really broken devices (contrary to the SCSI
* standards) need to be selected without asserting ATN
*/
if (*bflags & BLIST_SELECT_NO_ATN)
sdev->select_no_atn = 1;
/*
* Maximum 512 sector transfer length
* broken RA4x00 Compaq Disk Array
*/
if (*bflags & BLIST_MAX_512)
blk_queue_max_sectors(sdev->request_queue, 512);
/*
* Some devices may not want to have a start command automatically
* issued when a device is added.
*/
if (*bflags & BLIST_NOSTARTONADD)
sdev->no_start_on_add = 1;
if (*bflags & BLIST_SINGLELUN)
sdev->single_lun = 1;
sdev->use_10_for_rw = 1;
if (*bflags & BLIST_MS_SKIP_PAGE_08)
sdev->skip_ms_page_8 = 1;
if (*bflags & BLIST_MS_SKIP_PAGE_3F)
sdev->skip_ms_page_3f = 1;
if (*bflags & BLIST_USE_10_BYTE_MS)
sdev->use_10_for_ms = 1;
/* set the device running here so that slave configure
* may do I/O */
scsi_device_set_state(sdev, SDEV_RUNNING);
if (*bflags & BLIST_MS_192_BYTES_FOR_3F)
sdev->use_192_bytes_for_3f = 1;
if (*bflags & BLIST_NOT_LOCKABLE)
sdev->lockable = 0;
if (*bflags & BLIST_RETRY_HWERROR)
sdev->retry_hwerror = 1;
transport_configure_device(&sdev->sdev_gendev);
if (sdev->host->hostt->slave_configure) {
int ret = sdev->host->hostt->slave_configure(sdev);
if (ret) {
/*
* if LLDD reports slave not present, don't clutter
* console with alloc failure messages
*/
if (ret != -ENXIO) {
sdev_printk(KERN_ERR, sdev,
"failed to configure device\n");
}
return SCSI_SCAN_NO_RESPONSE;
}
}
/*
* Ok, the device is now all set up, we can
* register it and tell the rest of the kernel
* about it.
*/
if (!async && scsi_sysfs_add_sdev(sdev) != 0)
return SCSI_SCAN_NO_RESPONSE;
return SCSI_SCAN_LUN_PRESENT;
}
linux/driver/scsi/scsi_sysfs.c
/**
* scsi_sysfs_add_sdev - add scsi device to sysfs
* @sdev: scsi_device to add
*
* Return value:
* 0 on Success / non-zero on Failure
**/
int scsi_sysfs_add_sdev(struct scsi_device *sdev)
{
int error, i;
struct request_queue *rq = sdev->request_queue;
if ((error = scsi_device_set_state(sdev, SDEV_RUNNING)) != 0)
return error;
error = device_add(&sdev->sdev_gendev);
if (error) {
put_device(sdev->sdev_gendev.parent);
printk(KERN_INFO "error 1\n");
return error;
}
error = class_device_add(&sdev->sdev_classdev);
if (error) {
printk(KERN_INFO "error 2\n");
goto clean_device;
}
/* take a reference for the sdev_classdev; this is
* released by the sdev_class .release */
get_device(&sdev->sdev_gendev);
/* create queue files, which may be writable, depending on the host */
if (sdev->host->hostt->change_queue_depth)
error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_depth_rw);
else
error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_depth);
if (error) {
__scsi_remove_device(sdev);
goto out;
}
if (sdev->host->hostt->change_queue_type)
error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_type_rw);
else
error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_type);
if (error) {
__scsi_remove_device(sdev);
goto out;
}
error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL);
if (error)
sdev_printk(KERN_INFO, sdev,
"Failed to register bsg queue, errno=%d\n", error);
/* we're treating error on bsg register as non-fatal, so pretend
* nothing went wrong */
error = 0;
/* add additional host specific attributes */
if (sdev->host->hostt->sdev_attrs) {
for (i = 0; sdev->host->hostt->sdev_attrs[i]; i++) {
error = device_create_file(&sdev->sdev_gendev,
sdev->host->hostt->sdev_attrs[i]);
if (error) {
__scsi_remove_device(sdev);
goto out;
}
}
}
transport_add_device(&sdev->sdev_gendev);
out:
return error;
clean_device:
scsi_device_set_state(sdev, SDEV_CANCEL);
device_del(&sdev->sdev_gendev);
transport_destroy_device(&sdev->sdev_gendev);
put_device(&sdev->sdev_gendev);
return error;
}
上面的add_device可以执行本文章开头的probe函数,因为篇幅原因,就不继续深入分析,我们直接分析sd模块的probe函数:
/**
* sd_probe - called during driver initialization and whenever a
* new scsi device is attached to the system. It is called once
* for each scsi device (not just disks) present.
* @dev: pointer to device object
*
* Returns 0 if successful (or not interested in this scsi device
* (e.g. scanner)); 1 when there is an error.
*
* Note: this function is invoked from the scsi mid-level.
* This function sets up the mapping between a given
* (found in sdp) and new device name
* (e.g. /dev/sda). More precisely it is the block device major
* and minor number that is chosen here.
*
* Assume sd_attach is not re-entrant (for time being)
* Also think about sd_attach() and sd_remove() running coincidentally.
**/
static int sd_probe(struct device *dev)
{
struct scsi_device *sdp = to_scsi_device(dev);
struct scsi_disk *sdkp;
struct gendisk *gd;
u32 index;
int error;
error = -ENODEV;
if (sdp->type != TYPE_DISK && sdp->type != TYPE_MOD && sdp->type != TYPE_RBC)
goto out;
SCSI_LOG_HLQUEUE(3, sdev_printk(KERN_INFO, sdp,
"sd_attach\n"));
error = -ENOMEM;
sdkp = kzalloc(sizeof(*sdkp), GFP_KERNEL);
if (!sdkp)
goto out;
gd = alloc_disk(16);
if (!gd)
goto out_free;
if (!idr_pre_get(&sd_index_idr, GFP_KERNEL))
goto out_put;
spin_lock(&sd_index_lock);
error = idr_get_new(&sd_index_idr, NULL, &index);
spin_unlock(&sd_index_lock);
if (index >= SD_MAX_DISKS)
error = -EBUSY;
if (error)
goto out_put;
sdkp->device = sdp;
sdkp->driver = &sd_template;
sdkp->disk = gd;
sdkp->index = index;
sdkp->openers = 0;
if (!sdp->timeout) {
if (sdp->type != TYPE_MOD)
sdp->timeout = SD_TIMEOUT;
else
sdp->timeout = SD_MOD_TIMEOUT;
}
class_device_initialize(&sdkp->cdev);
sdkp->cdev.dev = &sdp->sdev_gendev;
sdkp->cdev.class = &sd_disk_class;
strncpy(sdkp->cdev.class_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE);
if (class_device_add(&sdkp->cdev))
goto out_put;
get_device(&sdp->sdev_gendev);
gd->major = sd_major((index & 0xf0) >> 4);
gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
gd->minors = 16;
gd->fops = &sd_fops;
if (index < 26) {
sprintf(gd->disk_name, "sd%c", 'a' + index % 26);
} else if (index < (26 + 1) * 26) {
sprintf(gd->disk_name, "sd%c%c",
'a' + index / 26 - 1,'a' + index % 26);
} else {
const unsigned int m1 = (index / 26 - 1) / 26 - 1;
const unsigned int m2 = (index / 26 - 1) % 26;
const unsigned int m3 = index % 26;
sprintf(gd->disk_name, "sd%c%c%c",
'a' + m1, 'a' + m2, 'a' + m3);
}
gd->private_data = &sdkp->driver;
gd->queue = sdkp->device->request_queue;
sd_revalidate_disk(gd);
blk_queue_prep_rq(sdp->request_queue, sd_prep_fn);
gd->driverfs_dev = &sdp->sdev_gendev;
gd->flags = GENHD_FL_DRIVERFS;
if (sdp->removable)
gd->flags |= GENHD_FL_REMOVABLE;
dev_set_drvdata(dev, sdkp);
add_disk(gd);
sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n",
sdp->removable ? "removable " : "");
return 0;
out_put:
put_disk(gd);
out_free:
kfree(sdkp);
out:
return error;
}
这个函数分配了盘符等必须的信息,但是还是没有完成与用户空间的接口设置,在函数执行快结束的地方我们能看见函数add_disk, 这个函数并不是属于scsi层的函数,而是linux/block的函数,我们知道硬盘等都是块设备,而我们从最底层一直到现在还没有接触任何与此相关的内容,linux/driver/block 和 linux/block即是与块设备的操作逻辑,而scsi层与libata只是为block层提供具体操作的接口。后续文章我们将继续深入分析。