linux libata初始化分析
进来分析libata模块,颇有所感,记录如下,希望能对大家有所帮助,同时也对自己的理解进一步深入。
linux版本:linux-2.6.24.3
注:因完全是个人理解,理解不当难免,恳请批评指正!!!!
大家知道驱动程序在初始化sata controller后, 并初始化ata_host结构体后,会调用函数ata_host_activate进入libata的初始化,我们从这里开始分析。
下面是freescale mpc8315平台的sata驱动代码。
linux/driver/ata/sata_fsl.c
static int sata_fsl_probe(struct of_device *ofdev,
const struct of_device_id *match)
{
host_priv = kzalloc(sizeof(struct sata_fsl_host_priv), GFP_KERNEL);
if (!host_priv)
goto error_exit_with_cleanup;
irq = irq_of_parse_and_map(ofdev->node, 0);
if (irq < 0) {
dev_printk(KERN_ERR, &ofdev->dev, "invalid irq from platform\n");
goto error_exit_with_cleanup;
}
host_priv->irq = irq;
/* allocate host structure */
host = ata_host_alloc_pinfo(&ofdev->dev, ppi, SATA_FSL_MAX_PORTS);
/* host->iomap is not used currently */
host->private_data = host_priv;
/* initialize host controller */
sata_fsl_init_controller(host);
/*
* Now, register with libATA core, this will also initiate the
* device discovery process, invoking our port_start() handler &
* error_handler() to execute a dummy Softreset EH session
*/
ata_host_activate(host, irq, sata_fsl_interrupt, SATA_FSL_IRQ_FLAG,
&sata_fsl_sht);
dev_set_drvdata(&ofdev->dev, host);
return 0;
}
函数ata_host_activate申请了中断,并调用ata_host_register函数注册host
linux/driver/ata/libata-core.c
/**
* ata_host_activate - start host, request IRQ and register it
* @host: target ATA host
* @irq: IRQ to request
* @irq_handler: irq_handler used when requesting IRQ
* @irq_flags: irq_flags used when requesting IRQ
* @sht: scsi_host_template to use when registering the host
*
* After allocating an ATA host and initializing it, most libata
* LLDs perform three steps to activate the host - start host,
* request IRQ and register it. This helper takes necessasry
* arguments and performs the three steps in one go.
*
* An invalid IRQ skips the IRQ registration and expects the host to
* have set polling mode on the port. In this case, @irq_handler
* should be NULL.
*
* LOCKING:
* Inherited from calling layer (may sleep).
*
* RETURNS:
* 0 on success, -errno otherwise.
*/
int ata_host_activate(struct ata_host *host, int irq,
irq_handler_t irq_handler, unsigned long irq_flags,
struct scsi_host_template *sht)
{
int i, rc;
rc = ata_host_start(host);
if (rc)
return rc;
/* Special case for polling mode */
if (!irq) {
WARN_ON(irq_handler);
return ata_host_register(host, sht);
}
rc = devm_request_irq(host->dev, irq, irq_handler, irq_flags,
dev_driver_string(host->dev), host);
if (rc)
return rc;
for (i = 0; i < host->n_ports; i++)
ata_port_desc(host->ports[i], "irq %d", irq);
rc = ata_host_register(host, sht);
/* if failed, just free the IRQ and leave ports alone */
if (rc)
devm_free_irq(host->dev, irq, host);
return rc;
}
linux/driver/ata/libata-core.c
/**
* ata_host_register - register initialized ATA host
* @host: ATA host to register
* @sht: template for SCSI host
*
* Register initialized ATA host. @host is allocated using
* ata_host_alloc() and fully initialized by LLD. This function
* starts ports, registers @host with ATA and SCSI layers and
* probe registered devices.
*
* LOCKING:
* Inherited from calling layer (may sleep).
*
* RETURNS:
* 0 on success, -errno otherwise.
*/
int ata_host_register(struct ata_host *host, struct scsi_host_template *sht)
{
int i, rc;
/* host must have been started */
if (!(host->flags & ATA_HOST_STARTED)) {
dev_printk(KERN_ERR, host->dev,
"BUG: trying to register unstarted host\n");
WARN_ON(1);
return -EINVAL;
}
/* Blow away unused ports. This happens when LLD can't
* determine the exact number of ports to allocate at
* allocation time.
*/
for (i = host->n_ports; host->ports[i]; i++)
kfree(host->ports[i]);
/* give ports names and add SCSI hosts */
for (i = 0; i < host->n_ports; i++)
host->ports[i]->print_id = ata_print_id++;
rc = ata_scsi_add_hosts(host, sht);
if (rc)
return rc;
/* associate with ACPI nodes */
ata_acpi_associate(host);
/* set cable, sata_spd_limit and report */
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
unsigned long xfer_mask;
/* set SATA cable type if still unset */
if (ap->cbl == ATA_CBL_NONE && (ap->flags & ATA_FLAG_SATA))
ap->cbl = ATA_CBL_SATA;
/* init sata_spd_limit to the current value */
sata_link_init_spd(&ap->link);
/* print per-port info to dmesg */
xfer_mask = ata_pack_xfermask(ap->pio_mask, ap->mwdma_mask,
ap->udma_mask);
if (!ata_port_is_dummy(ap)) {
ata_port_printk(ap, KERN_INFO,
"%cATA max %s %s\n",
(ap->flags & ATA_FLAG_SATA) ? 'S' : 'P',
ata_mode_string(xfer_mask),
ap->link.eh_info.desc);
ata_ehi_clear_desc(&ap->link.eh_info);
} else
ata_port_printk(ap, KERN_INFO, "DUMMY\n");
}
/* perform each probe synchronously */
DPRINTK("probe begin\n");
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
int rc;
/* probe */
if (ap->ops->error_handler) {
struct ata_eh_info *ehi = &ap->link.eh_info;
unsigned long flags;
ata_port_probe(ap);
/* kick EH for boot probing */
spin_lock_irqsave(ap->lock, flags);
ehi->probe_mask =
(1 << ata_link_max_devices(&ap->link)) - 1;
ehi->action |= ATA_EH_SOFTRESET;
ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET;
ap->pflags &= ~ATA_PFLAG_INITIALIZING;
ap->pflags |= ATA_PFLAG_LOADING;
ata_port_schedule_eh(ap);
spin_unlock_irqrestore(ap->lock, flags);
/* wait for EH to finish */
ata_port_wait_eh(ap);
} else {
DPRINTK("ata%u: bus probe begin\n", ap->print_id);
rc = ata_bus_probe(ap);
DPRINTK("ata%u: bus probe end\n", ap->print_id);
if (rc) {
/* FIXME: do something useful here?
* Current libata behavior will
* tear down everything when
* the module is removed
* or the h/w is unplugged.
*/
}
}
}
/* probes are done, now scan each port's disk(s) */
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
ata_scsi_scan_host(ap, 1);
ata_lpm_schedule(ap, ap->pm_policy);
}
return 0;
}
在ata_scsi_add_hosts函数启动了error_handler内核线程,之后会在红色第二部分代码执行该线程,直到初始华完毕,第三部分主要初始化每个硬盘设备(包括分配硬盘设备节点等)。
linux/driver/ata/libata-scsi.c
int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht)
{
int i, rc;
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
struct Scsi_Host *shost;
rc = -ENOMEM;
shost = scsi_host_alloc(sht, sizeof(struct ata_port *));
if (!shost)
goto err_alloc;
*(struct ata_port **)&shost->hostdata[0] = ap;
ap->scsi_host = shost;
shost->transportt = &ata_scsi_transport_template;
shost->unique_id = ap->print_id;
shost->max_id = 16;
shost->max_lun = 1;
shost->max_channel = 1;
shost->max_cmd_len = 16;
/* Schedule policy is determined by ->qc_defer()
* callback and it needs to see every deferred qc.
* Set host_blocked to 1 to prevent SCSI midlayer from
* automatically deferring requests.
*/
shost->max_host_blocked = 1;
rc = scsi_add_host(ap->scsi_host, ap->host->dev);
if (rc)
goto err_add;
}
return 0;
err_add:
scsi_host_put(host->ports[i]->scsi_host);
err_alloc:
while (--i >= 0) {
struct Scsi_Host *shost = host->ports[i]->scsi_host;
scsi_remove_host(shost);
scsi_host_put(shost);
}
return rc;
}
ata_scsi_add_hosts主要初始化scsi层需要的结构,然后注册到scsi模块,完成scsi与ata的连接。
linux/driver/scsi/hosts.c
/**
* scsi_host_alloc - register a scsi host adapter instance.
* @sht: pointer to scsi host template
* @privsize: extra bytes to allocate for driver
*
* Note:
* Allocate a new Scsi_Host and perform basic initialization.
* The host is not published to the scsi midlayer until scsi_add_host
* is called.
*
* Return value:
* Pointer to a new Scsi_Host
**/
struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{
struct Scsi_Host *shost;
gfp_t gfp_mask = GFP_KERNEL;
int rval;
if (sht->unchecked_isa_dma && privsize)
gfp_mask |= __GFP_DMA;
shost = kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);
if (!shost)
return NULL;
shost->host_lock = &shost->default_lock;
spin_lock_init(shost->host_lock);
shost->shost_state = SHOST_CREATED;
INIT_LIST_HEAD(&shost->__devices);
INIT_LIST_HEAD(&shost->__targets);
INIT_LIST_HEAD(&shost->eh_cmd_q);
INIT_LIST_HEAD(&shost->starved_list);
init_waitqueue_head(&shost->host_wait);
mutex_init(&shost->scan_mutex);
shost->host_no = scsi_host_next_hn++; /* XXX(hch): still racy */
shost->dma_channel = 0xff;
/* These three are default values which can be overridden */
shost->max_channel = 0;
shost->max_id = 8;
shost->max_lun = 8;
/* Give each shost a default transportt */
shost->transportt = &blank_transport_template;
/*
* All drivers right now should be able to handle 12 byte
* commands. Every so often there are requests for 16 byte
* commands, but individual low-level drivers need to certify that
* they actually do something sensible with such commands.
*/
shost->max_cmd_len = 12;
shost->hostt = sht;
shost->this_id = sht->this_id;
shost->can_queue = sht->can_queue;
shost->sg_tablesize = sht->sg_tablesize;
shost->cmd_per_lun = sht->cmd_per_lun;
shost->unchecked_isa_dma = sht->unchecked_isa_dma;
shost->use_clustering = sht->use_clustering;
shost->ordered_tag = sht->ordered_tag;
shost->active_mode = sht->supported_mode;
shost->use_sg_chaining = sht->use_sg_chaining;
if (sht->supported_mode == MODE_UNKNOWN)
/* means we didn't set it ... default to INITIATOR */
shost->active_mode = MODE_INITIATOR;
else
shost->active_mode = sht->supported_mode;
if (sht->max_host_blocked)
shost->max_host_blocked = sht->max_host_blocked;
else
shost->max_host_blocked = SCSI_DEFAULT_HOST_BLOCKED;
/*
* If the driver imposes no hard sector transfer limit, start at
* machine infinity initially.
*/
if (sht->max_sectors)
shost->max_sectors = sht->max_sectors;
else
shost->max_sectors = SCSI_DEFAULT_MAX_SECTORS;
/*
* assume a 4GB boundary, if not set
*/
if (sht->dma_boundary)
shost->dma_boundary = sht->dma_boundary;
else
shost->dma_boundary = 0xffffffff;
rval = scsi_setup_command_freelist(shost);
if (rval)
goto fail_kfree;
device_initialize(&shost->shost_gendev);
snprintf(shost->shost_gendev.bus_id, BUS_ID_SIZE, "host%d",
shost->host_no);
shost->shost_gendev.release = scsi_host_dev_release;
class_device_initialize(&shost->shost_classdev);
shost->shost_classdev.dev = &shost->shost_gendev;
shost->shost_classdev.class = &shost_class;
snprintf(shost->shost_classdev.class_id, BUS_ID_SIZE, "host%d",
shost->host_no);
shost->ehandler = kthread_run(scsi_error_handler, shost,
"scsi_eh_%d", shost->host_no);
if (IS_ERR(shost->ehandler)) {
rval = PTR_ERR(shost->ehandler);
goto fail_destroy_freelist;
}
scsi_proc_hostdir_add(shost->hostt);
return shost;
fail_destroy_freelist:
scsi_destroy_command_freelist(shost);
fail_kfree:
kfree(shost);
return NULL;
}
EXPORT_SYMBOL(scsi_host_alloc);
scsi_alloc_hosts执行完,内核即多了一个线程执行scsi_error_handler, ata_scsi_add_hosts继续初始话scsi_host结构体,其中:
shost->transportt = &ata_scsi_transport_template;会在scsi_error_handler调用。
/**
* scsi_error_handler - SCSI error handler thread
* @data: Host for which we are running.
*
* Notes:
* This is the main error handling loop. This is run as a kernel thread
* for every SCSI host and handles all error handling activity.
**/
int scsi_error_handler(void *data)
{
struct Scsi_Host *shost = data;
/*
* We use TASK_INTERRUPTIBLE so that the thread is not
* counted against the load average as a running process.
* We never actually get interrupted because kthread_run
* disables singal delivery for the created thread.
*/
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
if ((shost->host_failed == 0 && shost->host_eh_scheduled == 0) ||
shost->host_failed != shost->host_busy) {
SCSI_LOG_ERROR_RECOVERY(1,
printk("Error handler scsi_eh_%d sleeping\n",
shost->host_no));
schedule();
set_current_state(TASK_INTERRUPTIBLE);
continue;
}
__set_current_state(TASK_RUNNING);
SCSI_LOG_ERROR_RECOVERY(1,
printk("Error handler scsi_eh_%d waking up\n",
shost->host_no));
/*
* We have a host that is failing for some reason. Figure out
* what we need to do to get it up and online again (if we can).
* If we fail, we end up taking the thing offline.
*/
if (shost->transportt->eh_strategy_handler)
shost->transportt->eh_strategy_handler(shost);
else
scsi_unjam_host(shost);
/*
* Note - if the above fails completely, the action is to take
* individual devices offline and flush the queue of any
* outstanding requests that may have been pending. When we
* restart, we restart any I/O to any other devices on the bus
* which are still online.
*/
scsi_restart_operations(shost);
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
SCSI_LOG_ERROR_RECOVERY(1,
printk("Error handler scsi_eh_%d exiting\n", shost->host_no));
shost->ehandler = NULL;
return 0;
}