• 【转】linux libata初始化分析


    转自:http://zhangwenxin82.blog.163.com/blog/static/114595956201071934831530/

    进来分析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;
    }

  • 相关阅读:
    《30天自制操作系统》17_day_学习笔记
    《30天自制操作系统》18_day_学习笔记
    湖大OJ-实验E----可判定的DFA的空问题
    湖大OJ-实验C----NFA转换为DFA
    《30天自制操作系统》16_day_学习笔记
    《30天自制操作系统》19_day_学习笔记
    《30天自制操作系统》15_day_学习笔记
    《30天自制操作系统》14_day_学习笔记
    [Leetcode Week11]Kth Largest Element in an Array
    [Leetcode Week10]Minimum Time Difference
  • 原文地址:https://www.cnblogs.com/GoodGoodWorkDayDayUp/p/2254024.html
Copyright © 2020-2023  润新知