Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15890
  • 博文数量: 9
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2016-10-14 18:13
个人简介

淡泊明志、宁静致远

文章存档

2016年(9)

我的朋友

分类: LINUX

2016-11-01 21:24:26

原文地址:linux libata初始化分析 作者:zhenchengjin

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;
}

阅读(520) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~