目录

概念

port

link

device

tag

scsi host

scsi device

sgl

Port、Link、Device之间的关系

Port

Link

host link

pmp link

Device

初始化流程

异常流程

ops

ata_port_operations

scsi_host_temp

Shadow Registers / Task File Registers

libata内部使用的ATA命令发送接口

ata_exec_internal、ata_exec_internal_sg

SCSI层发送ATA命令

设备与驱动匹配过程复习

ata_scsi_queuecmd

ata_std_qc_defer

scsi_execute_req 同步命令访问

命令完成 ata_qc_complete 、 ata_qc_complete_multiple

tag active相关变量

核心ATA发送命令 ata_qc_issue

ahci_qc_prep

ahci_qc_issue

端口恢复 ahci_port_resume

ahci_stop_engine

ahci_start_engine

ahci_stop_fis_rx

ahci_error_handler

sata_pmp_error_handler

ahci_port_suspend


非AHCI模式下的SATA驱动不同芯片实现方式很不同,规范未规定软件如何组装FIS,如何组装PRD。

如fsl的sata控制器中,命令表为CFIS(32B)+SFIS(32B)+ACMD(16B)+RSRVD(16B)+PRD(63)*16;
有些控制器的CFIS和其他其他字段可能根本不连续。

PRD的定义不同芯片也不一样。AHCI规范就规定了所有的地址和定义,让驱动统一了起来;
ahci的命令表为CFIS(64B)+ACMD(16B)+RSRVD(48B)+PRD(64KB)*16;

概念

port

一个AHCI HBA可支持最多32个sata port。

link

一个sata port可通过Port Multiplier连接最多15个link。

device

从linux驱动源码来看,一个link可连接2个设备,但一个link时如何连接2个设备的呢?

tag

SATA设备NCQ模式下一次最多同时存在32个待处理的命令,使用tag来标记每个命令的序号,驱动代码中有些地方使用tag数值表示某个命令,有些地方使用bitmask来表示哪些命令正在处理。

scsi host

scsi主机,在ata-scsi中,一个port就是一个scsi host,在ata_scsi_add_hosts函数中创建。

ahci_init_one
    -> ata_host_activate
        -> ata_host_register
            -> ata_scsi_add_hosts

-> for (i = 0; i < n_ports; i++) {

scsi_host_alloc();

scsi_add_host_with_dma();

}

-> async_port_probe

scsi device

scsi设备。在ata-scsi中,对应一个port的一个link,也就是ata的一个pmp。

ahci_init_one
    -> ata_host_activate
        -> ata_host_register
                -> ata_scsi_add_hosts

-> async_port_probe //每个端口

-> ata_port_probe(ap);

-> ata_scsi_scan_host;
                                 -> __scsi_add_device;

ata_scsi_port_error_handler

->ata_scsi_hotplug //通过schedule_delayed_work唤醒

-> ata_scsi_handle_link_detach

-> ata_scsi_scan_host;

sgl

参考文章IOMMU/SMMUV3代码分析(8)函数dma_map_sg()_linux解码者的博客-CSDN博客

集散链表。sg类型为scatterlist,表示一小块连续物理内存。多个sg组成一个链表就叫做sgl,sgl表述一片分散的物理内存。

结构体scatterlist包含从CPU和IO设备角度看到的一块连续的物理内存区域的地址和长度。其中dma_addrss为设备看到的虚拟地址即IOVA,page_link为CPU看到的虚拟地址即VA,length为数据长度,offset为数据在页中的偏移。(当前单个scattherlist表示最多为一页)

struct scatterlist {unsigned long    page_link;//CPU侧看到的物理地址(以page为单位,只能表述是第哪个page)unsigned int   offset;//CPU侧看到的物理地址(page_link+offset,能具体表述那个字节)unsigned int    length;//本断数据的长度dma_addr_t  dma_address;//设备侧看到的DMA虚拟地址
#ifdef CONFIG_NEED_SG_DMA_LENGTHunsigned int    dma_length;
#endif
};

dma_map_sg

将旧sgl中多个分散的物理地址映射为连续ivoa地址,iova被赋值到dma_address变量中,组成新的sgl,新sgl的sg数量比旧sgl的sg数量更少。

0-> ata_sg_setup1-> dma_map_sg2->ops->map_sg //如果CPU支持IOMMU则__iommu_map_sg_attrs,否则__swiotlb_map_sg_attrs0-> __iommu_map_sg_attrs1-> iommu_dma_map_sg

Port、Link、Device之间的关系

一个AHCI包含最多32个host port,每个host port可使用Port Multiplier芯片扩展出最多15个Link,每个host port也是scsi设备的host,每个Link上可接一个Device(如SSD或其他ata设备)。Linux驱动为何统一,即使没有使用Port Multiplier来扩展link,驱动也会视作端口有一个link(我称之为host link),然后通过link连接Device

AHCI HBA -> port0-31 -> link0-15 -> device

Port

由ata_port_alloc分配,结构体定义如下


struct ata_port {struct Scsi_Host   *scsi_host; /* our co-allocated scsi host */struct ata_port_operations *ops;spinlock_t      *lock;/* Flags owned by the EH context. Only EH should touch these once theport is active */unsigned long       flags;  /* ATA_FLAG_xxx *//* Flags that change dynamically, protected by ap->lock */unsigned int     pflags; /* ATA_PFLAG_xxx */unsigned int     print_id; /* user visible unique port ID */unsigned int            local_port_no; /* host local port num */unsigned int     port_no; /* 0 based port no. inside the host */struct ata_ioports   ioaddr; /* ATA cmd/ctl/dma register blocks */u8         ctl;    /* cache of ATA control register */u8           last_ctl;   /* Cache last written value */struct ata_link*  sff_pio_task_link; /* link currently used */struct delayed_work sff_pio_task;struct ata_bmdma_prd   *bmdma_prd; /* BMDMA SG list */dma_addr_t       bmdma_prd_dma;  /* and its DMA mapping */unsigned int       pio_mask;unsigned int       mwdma_mask;unsigned int     udma_mask;unsigned int      cbl;    /* cable type; ATA_CBL_xxx */struct ata_queued_cmd  qcmd[ATA_MAX_QUEUE + 1];unsigned long      sas_tag_allocated; /* for sas tag allocation only */u64         qc_active;int           nr_active_links; /* #links with active qcs */unsigned int       sas_last_tag;   /* track next tag hw expects */struct ata_link      link;       /* host default link ,host端口用的link*/struct ata_link      *slave_link;    /* see ata_slave_link_init() */int          nr_pmp_links;   /* nr of available PMP links */struct ata_link      *pmp_link;  /* array of PMP links */struct ata_link     *excl_link; /* for PMP qc exclusion */struct ata_port_stats stats;struct ata_host       *host;struct device         *dev;struct device      tdev;struct mutex       scsi_scan_mutex;struct delayed_work hotplug_task;struct work_struct scsi_rescan_task;unsigned int       hsm_task_state;u32          msg_enable;struct list_head eh_done_q;wait_queue_head_t eh_wait_q;int           eh_tries;struct completion  park_req_pending;pm_message_t       pm_mesg;enum ata_lpm_policy target_lpm_policy;struct timer_list fastdrain_timer;unsigned long       fastdrain_cnt;int           em_message_type;void            *private_data;struct ata_acpi_gtm   __acpi_init_gtm; /* use ata_acpi_init_gtm() *//* owned by EH */u8           sector_buf[ATA_SECT_SIZE] ____cacheline_aligned;
};

Link

host link

ata_port.link就是一个host port的host link,无论是否有pmp端口,该link一定存在,用于处理该host port的操作,很多操作只能host port来处理,不能使用pmp端口。.pmp=0,但在使用时,一般会linkno = 15。

pmp link

在系统运行期间通过sata_pmp_attach->sata_pmp_init_links->ata_link_init接口来初始化15个link并使用port->pmp_link指向这15个link的数组,.pmp值为0~14。

结构体定义如下

struct ata_link {struct ata_port     *ap;int         pmp;        /* port multiplier port # */struct device       tdev;unsigned int       active_tag; /* active tag on this link */u32            sactive;    /* active NCQ commands */unsigned int       flags;      /* ATA_LFLAG_xxx */u32          saved_scontrol; /* SControl on probe */unsigned int     hw_sata_spd_limit;unsigned int      sata_spd_limit;unsigned int     sata_spd;   /* current SATA PHY speed */enum ata_lpm_policy lpm_policy;/* record runtime error info, protected by host_set lock */struct ata_eh_info    eh_info;/* EH context */struct ata_eh_context   eh_context;struct ata_device    device[ATA_MAX_DEVICES];//ATA_MAX_DEVICES=2unsigned long       last_lpm_change; /* when last LPM change happened */
};

Device

ata_link_init->ata_dev_init来初始化一个link的两个设备

结构体定义如下

struct ata_device {struct ata_link       *link;unsigned int      devno;      /* 0 or 1 */unsigned int        horkage;    /* List of broken features */unsigned long      flags;      /* ATA_DFLAG_xxx */struct scsi_device   *sdev;      /* attached SCSI device */void          *private_data;union acpi_object *gtf_cache;unsigned int     gtf_filter;struct device        tdev;/* n_sector is CLEAR_BEGIN, read comment above CLEAR_BEGIN */u64           n_sectors;  /* size of device, if ATA */u64         n_native_sectors; /* native size, if ATA */unsigned int     class;      /* ATA_DEV_xxx */unsigned long      unpark_deadline;u8          pio_mode;u8         dma_mode;u8         xfer_mode;unsigned int      xfer_shift; /* ATA_SHIFT_xxx */unsigned int     multi_count;    /* sectors count forREAD/WRITE MULTIPLE */unsigned int      max_sectors;    /* per-device max sectors */unsigned int        cdb_len;/* per-dev xfer mask */unsigned long        pio_mask;unsigned long      mwdma_mask;unsigned long        udma_mask;/* for CHS addressing */u16           cylinders;  /* Number of cylinders */u16            heads;      /* Number of heads */u16            sectors;    /* Number of sectors per track */union {u16     id[ATA_ID_WORDS]; /* IDENTIFY xxx DEVICE data */u32     gscr[SATA_PMP_GSCR_DWORDS]; /* PMP GSCR block */} ____cacheline_aligned;/* DEVSLP Timing Variables from Identify Device Data Log */u8           devslp_timing[ATA_LOG_DEVSLP_SIZE];/* NCQ send and receive log subcommand support */u8          ncq_send_recv_cmds[ATA_LOG_NCQ_SEND_RECV_SIZE];u8           ncq_non_data_cmds[ATA_LOG_NCQ_NON_DATA_SIZE];/* ZAC zone configuration */u32            zac_zoned_cap;u32           zac_zones_optimal_open;u32          zac_zones_optimal_nonseq;u32            zac_zones_max_open;/* error history */int           spdn_cnt;/* ering is CLEAR_END, read comment above CLEAR_END */struct ata_ering ering;
};

初始化流程

待补充

异常流程

异常处理的初始化:sata_port_ops.sched_eh=ata_std_sched_eh(libata-core.c);scsi_host_alloc->.ehandler=scsi_error_handler(scsi/hosts.c);subsystem init->ata_init->ata_attach_transport(libata_core.c)->.eh_strategy_handler=ata_scsi_error(libata_eh.c);ata_qc_complete(ATA_CMD_SET_MULTI)(libata-core.c)->ata_port_schedule_eh(libata-eh.c);ata_port_probe(libata-core.c)->__ata_port_probe(libata-core.c)->ata_port_schedule_eh(libata-eh.c);ata_port_detach(libata-core.c)->ata_port_schedule_eh(libata-eh.c);ata_do_link_abort(libata-eh.c)->ata_port_schedule_eh(libata-eh.c);异常处理流程:
ahci_handle_port_interrupt(libahci.c)->sata_async_notification(libata-eh.c)->ata_port_schedule_eh(libata-eh.c)->ata_std_sched_eh(libata-eh.c) //.sched_eh->scsi_schedule_eh(scsi/scsi_error.c)->scsi_error_handler //SCSI的主错误处理内核态线程, 通过scsi_eh_wakeup唤醒(scsi/hosts.c))->ata_scsi_error(libata_eh.c) //.eh_strategy_handler->ata_scsi_port_error_handler(libata_eh.c)->ata_std_error_handler(libata_eh.c) //.error_handler->ata_do_eh(libata_eh.c)->ata_eh_recover(libata_eh.c)->ata_eh_reset(libata_eh.c)->ata_std_postreset(libata_core.c) //.postreset->sata_print_link_status(libata_core.c)

ops

ata_port_operations

//libata-core.c
const struct ata_port_operations sata_port_ops = {.inherits        = &ata_base_port_ops,.qc_defer     = ata_std_qc_defer,.hardreset      = sata_std_hardreset,
};//libata-pmp.c
const struct ata_port_operations sata_pmp_port_ops = {.inherits        = &sata_port_ops,.pmp_prereset     = ata_std_prereset,.pmp_hardreset      = sata_std_hardreset,.pmp_postreset        = ata_std_postreset,.error_handler     = sata_pmp_error_handler,
};//libahci.c
struct ata_port_operations ahci_ops = {.inherits       = &sata_pmp_port_ops,.qc_defer     = ahci_pmp_qc_defer,.qc_prep       = ahci_qc_prep,.qc_issue       = ahci_qc_issue,.qc_fill_rtf       = ahci_qc_fill_rtf,.freeze         = ahci_freeze,//关闭端口中断使能寄存器(0x14).thaw         = ahci_thaw,//清除端口中断状态(0x10),并打开端口中断使能寄存器(0x14).softreset       = ahci_softreset,.hardreset        = ahci_hardreset,.postreset        = ahci_postreset,.pmp_softreset        = ahci_softreset,.error_handler        = ahci_error_handler,.post_internal_cmd    = ahci_post_internal_cmd,.dev_config       = ahci_dev_config,.scr_read        = ahci_scr_read,//sata 标准寄存器读.scr_write        = ahci_scr_write,//sata 标准寄存器写.pmp_attach      = ahci_pmp_attach,.pmp_detach      = ahci_pmp_detach,.set_lpm     = ahci_set_lpm,.em_show        = ahci_led_show,.em_store      = ahci_led_store,.sw_activity_show = ahci_activity_show,.sw_activity_store    = ahci_activity_store,.transmit_led_message    = ahci_transmit_led_message,
#ifdef CONFIG_PM.port_suspend       = ahci_port_suspend,.port_resume       = ahci_port_resume,
#endif.port_start       = ahci_port_start,//在 ahci_host_activate->ata_host_activate->ata_host_start(libata_core.c) 中调用.port_stop     = ahci_port_stop,//在 ata_host_stop (libata_core.c) 中调用
};

scsi_host_temp

//libata.h
#define ATA_BASE_SHT(drv_name)                  \.module            = THIS_MODULE,         \.name          = drv_name,            \.ioctl         = ata_scsi_ioctl,      \.queuecommand      = ata_scsi_queuecmd,       \.can_queue     = ATA_DEF_QUEUE,       \.tag_alloc_policy  = BLK_TAG_ALLOC_RR,        \.this_id       = ATA_SHT_THIS_ID,     \.emulated      = ATA_SHT_EMULATED,        \.use_clustering        = ATA_SHT_USE_CLUSTERING,  \.proc_name     = drv_name,            \.slave_configure   = ata_scsi_slave_config,   \.slave_destroy     = ata_scsi_slave_destroy,  \.bios_param        = ata_std_bios_param,      \.unlock_native_capacity    = ata_scsi_unlock_native_capacity, \.sdev_attrs        = ata_common_sdev_attrs#define ATA_NCQ_SHT(drv_name)                   \ATA_BASE_SHT(drv_name),                    \.change_queue_depth    = ata_scsi_change_queue_depth//ahci.h
#define AHCI_SHT(drv_name)                      \ATA_NCQ_SHT(drv_name),                     \.can_queue     = AHCI_MAX_CMDS,           \.sg_tablesize      = AHCI_MAX_SG,             \.dma_boundary      = AHCI_DMA_BOUNDARY,           \.shost_attrs       = ahci_shost_attrs,            \.sdev_attrs        = ahci_sdev_attrs//ahci.c
static struct scsi_host_template ahci_sht = {AHCI_SHT("ahci"),
};

Shadow Registers / Task File Registers

设备中有一个Task File结构,用于与主机交互命令,源码定义于libata.h

struct ata_taskfile {unsigned long       flags;      /* ATA_TFLAG_xxx */u8           protocol;   /* ATA_PROT_xxx */u8            ctl;        /* control reg */u8         hob_feature;    /* additional data */u8         hob_nsect;  /* to support LBA48 */u8            hob_lbal;u8         hob_lbam;u8         hob_lbah;u8         feature;u8          nsect;u8            lbal;u8         lbam;u8         lbah;u8         device;u8           command;    /* IO operation */u32           auxiliary;  /* auxiliary field *//* from SATA 3.1 and *//* ATA-8 ACS-3 */
};

libata内部使用的ATA命令发送接口

libata中使用tf(taskfile)作为入参来描述一条命令。

ata_exec_internal、ata_exec_internal_sg

前者使用buf、后者使用sg来指向数据内存。发送tf中的命令给设备。

此接口会将tf结构转换为qc(struct ata_queued_cmd)队列命令,再交由ata_qc_issue来完成命令的发送。

这两个内部发送命令固定使用第一个队列命令,qc->tag = ATA_TAG_INTERNAL=32; qc->hw_tag = 0。

//libata-core.c
0-> ata_exec_internal1-> ata_exec_internal_sg //tf转换为qc后发送ata命令2-> __ata_qc_from_tag //获取tag=ATA_TAG_INTERNAL专用qc2-> qc->complete_fn = ata_qc_complete_internal;2-> ata_qc_issue2-> wait_for_completion_timeout //等待完成命令完成2-> ata_qc_free //释放qc资源0-> 完成中断1-> ata_qc_complete_internal2-> complete(waiting); //通过调用complete来唤醒等待线程

SCSI层发送ATA命令

scsi中使用scsi_cmnd来描述一条命令,scsi_cmnd中也有一个tag用于标记队列命令,功能与taskfile中的hw_tag类似。

ata core注册的scsi接口如下

//ahci.c

static struct scsi_host_template ahci_sht = {

AHCI_SHT("ahci"),

};

其中

.queuecommand = ata_scsi_queuecmd

/**********初始化阶段**********/
//mq模式下 初始化scsi阶段就已经决定了发送接口
ata_scsi_add_hosts-> scsi_add_host_with_dma //每个端口都会执行-> scsi_mq_setup_tags-> shost->tag_set.ops = &scsi_mq_ops; //{.queue_rq  = scsi_queue_rq,...}init_sd (scsi/sd.c)-> register_blkdev //执行16次,注册16个sd块设备-> scsi_register_driver(&sd_template.gendrv); //sd_template={.gendrv = {.probe = sd_probe,...},...}-> drv->bus = &scsi_bus_type; //scsi_bus_type={.match = scsi_bus_match,,...}-> driver_register//注册scsi总线驱动,总线类型为scsi_bus_type,此总线类型的match函数中会匹配dev->type == &scsi_dev_type/**********初始化阶段或运行阶段**********/
ata_scsi_scan_host-> __scsi_add_device-> scsi_probe_and_add_lun //探测并添加逻辑单元号(Logic Unit Number),lun范围[1,512]-> scsi_alloc_sdev-> scsi_sysfs_device_initialize-> sdev->sdev_gendev.bus = &scsi_bus_type;-> sdev->sdev_gendev.type = &scsi_dev_type;//if(使用blk的mq模式)-> scsi_mq_alloc_queueelse-> scsi_old_alloc_queueq->request_fn = scsi_request_fn-> scsi_probe_lun //通过scsi_execute_req获取设备inquiry-> scsi_add_lun-> scsi_sysfs_add_sdev-> device_add(&sdev->sdev_gendev); //之前已经把bus和type都配置了,此接口在match成功后,会调用驱动的probe函数...-> sd_probe //完成创建磁盘和与设备关联的相关操作,完成后在用户层面就可以看到磁盘/**********运行阶段**********/
sd_probe(scsi/sd.c)-> alloc_disk    //分配genhd数据结构并设置相关属性-> sd_probe_async-> sd_revalidate_disk-> sd_spinup_disk //启动disk,TEST_UNIT_READY命令-> sd_read_capacity //获取盘信息,capacity容量、sector_size和physical_block_size,并配置给request_queue-> sd_print_capacity //打印磁盘信息-> device_add_disk //将磁盘添加到系统//mq模式
scsi_queue_rq-> cmd->scsi_done = scsi_mq_done;-> scsi_dispatch_cmd-> host->hostt->queuecommand(host, cmd); //ata_scsi_queuecmd//非mq模式
scsi_request_fn-> cmd->scsi_done = scsi_done;-> scsi_dispatch_cmd-> host->hostt->queuecommand(host, cmd); //ata_scsi_queuecmd

IO层面

sector:盘扇区,一般512

logical blocks:盘逻辑块,就是sector,有些地方叫logic sector,一般512

physical blocks:盘物理块,一般hhd 512、ssd 4K,为逻辑块整数倍,操作一个物理块和操作一个逻辑块耗时相同。

文件系统层面

块:windows中又叫簇,在创建文件系统时指定,为盘物理块整数倍,

设备与驱动匹配过程复习

设备注册device_add和驱动注册driver_register都会调用的bus_type中的match接口来匹配驱动与设备,匹配成功则调用bus_type中的probe或者驱动中的probe,一旦匹配成功,dev->driver就会设置为匹配驱动。

        如果是pci这样真实物理总线,pci_bus_match函数会根据drv的id_table来匹配pci总线上的设备;

        如果是平台总线,platform_match函数会先用drv.of_device_id来匹配,只要.name .type .compatible三个参数中的任何一个匹配成功则匹配成功,优先级 .compatible> .type>.name;如果匹配失败,则匹配acpi;如果匹配失败,则根据drv的id_table来匹配,此处的id_table与pci总线的id_table不同,包含一个字符串和一个指针,其实还是通过字符串来匹配的,并不是设备id

一、先注册驱动,再注册设备
        1、注册驱动需指定device_driver.bus_type,bus_type设置.name和.match,然后driver_register注册驱动

2、注册设备需指定device.bus_type和device.device_type,然后调用device_add注册设备。

device_add

->bus_add_device //将设备注册进指定的bus_type中

->bus_probe_device//匹配设备与驱动并probe

->device_initial_probe->__device_attach

->bus_for_each_drv 为此bus_type的所有驱动调用__device_attach_driver(drv, data)

->__device_attach_driver

->driver_match_device //调用bus_type中的.match接口匹配设备

->driver_probe_device //调用probe

-> if (dev->bus->probe) dev->bus->probe(dev);

else if (drv->probe) drv->probe(dev);//dev->bus->probe和drv->probe同时不为空时属于异常情况,driver_register时会警告

__device_attach()
{//如果此设备有驱动,really_probe中会dev->driver = drv;所以一个设备最多只能匹配到一个驱动if (dev->driver) {ret = device_bind_driver(dev); }//没有驱动去寻找驱动匹配else {ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach_driver); //设备匹配驱动}
}

二、先注册设备,再注册驱动

1、注册设备需指定device.bus_type和device.device_type,然后调用device_add注册设备

2、注册驱动需指定device_driver.bus_type,bus_type设置.name和.match,然后driver_register注册驱动

driver_register

->bus_add_driver

->driver_attach

->bus_for_each_dev //为此bus_type的所有设备调用__driver_attach

->__driver_attach

->driver_match_device

->driver_probe_device

ata_scsi_queuecmd

0-> ata_scsi_queuecmd1-> __ata_scsi_queuecmd2-> ata_scsi_translate //scsi命令转化为ATA命令后发出SCSI命令到ATA设备。3-> 根据scmd.cmnd获取转化函数xlat_func3-> ata_scsi_qc_new //通过scsi命令获取qc,并初始化4-> ata_qc_new_init5-> __ata_qc_from_tag //通过tag号获取qc4-> qc->scsidone = cmd->scsi_done; //将scsi命令的完成回调函数赋值给qc3-> ata_sg_init //scsi命令中获取sg链表3-> qc->complete_fn = ata_scsi_qc_complete;3-> xlat_func(qc); // 将scsi命令转化为ata命令,如果xlat_func==atapi_xlat4-> qc->complete_fn = atapi_qc_complete;3-> ap->ops->qc_defer(qc) //ata_std_qc_defer 检测qc是否需要被延迟(deferred),被延时的命令会被返回繁忙错误,等待再次被发送3-> ata_qc_issue0-> 完成中断1-> ata_scsi_qc_complete 或 atapi_qc_complete2-> ata_qc_done3-> ata_qc_free(qc); //释放qc资源3-> qc->scsidone(qc->scsicmd); //调用scsi层的完成函数

ata_std_qc_defer

判断当前link是否繁忙,是否需要延迟处理当前命令:

上一个命令是非NCQ模式且未完成,则需要延迟处理。

int ata_std_qc_defer(struct ata_queued_cmd *qc)
{struct ata_link *link = qc->dev->link;if (ata_is_ncq(qc->tf.protocol)) {//NCQ模式下,link->active_tag存放的是上一条非NCQ模式命令的活跃状态。//即NCQ模式下,上一条非NCQ未完成的if (!ata_tag_valid(link->active_tag))return 0;} else {//非NCQ模式下,link->active_tag表示正在执行的命令tag号,link->sactive固定=0if (!ata_tag_valid(link->active_tag) && !link->sactive)return 0;}return ATA_DEFER_LINK;
}

scsi_execute_req 同步命令访问

通过blk层来调用的访问接口
scsi_execute_req-> scsi_execute -> __scsi_execute-> blk_get_request //从request_queue中分配空闲的request-> scsi_req //request -> scsi_request-> blk_rq_map_kern // 映射内核数据到一个块设备驱动层请求。buffer -> bio,bio通过blk_rq_append_bio放入到request中。还有一个对应用户数据接口blk_rq_map_user,映射用户数据到一个块设备驱动层请求-> blk_execute_rq //将request插入到I/O调度器队列,at_head==1说明将request插入到队列头部blk_execute_rq //是块I/O子系统提供的公共函数,此接口会等待命令完成-> blk_execute_rq_nowait //此接口不会等待命令完成,通过done回调函数中complete来唤醒后面的wait_for_completion_io等待-> blk_mq_sched_insert_request //Linux block 层IO请求处理过程,流程略-> wait_for_completion_io

Linux block 层IO请求处理过程参考

blk_rq_map_kern将内核数据映射到request。

• int blk_rq_map_kern(struct request_queue *, struct request *, void *,unsigned int, gfp_t)映射内核数据到一个块设备驱动层请求,用于REQ_TYPE_BLOCK_PC;

• int blk_rq_map_user(struct request_queue *, struct request *, structrq_map_data *, void __user *, unsigned long, gfp_t)映射用户数据到一个块设备驱动层请求,用户与REQ_TYPE_BLOCK_PC;

• int blk_rq_map_sg(struct request_queue *, struct request *, structscatterlist *)映射一个块设备驱动层请求到聚散列表;

• int blk_rq_map_integrity_sg(struct request *, struct scatterlist *)映射块设备驱动层请求中的完整性向量到聚散列表。

命令完成 ata_qc_complete 、 ata_qc_complete_multiple

命令发起过程出错后调用 ata_qc_complete
命令处理完成后的中断函数中调用 ata_qc_complete_multiple->ata_qc_complete0-> ahci_handle_port_interrupt //读取SActive(SCR3)寄存器获取qc_active1-> ata_qc_complete_multiple //将qc_active转换为完成命令的tag2-> ata_qc_from_tag //通过tag获取命令qc2-> ata_qc_complete3-> __ata_qc_complete4-> ata_sg_clean //清除qc中的sg指针4-> link->sactive &= ~(1 << qc->hw_tag); //清除NCQ的对应tag4-> qc->complete_fn 调用执行ata_qc_issue前注册的完成函数

tag active相关变量

link->active_tag; 用于非NCQ,0~32

数值型

表示非NCQ模式正在执行的命令tag, 等于ATA_TAG_POISON(0xfafbfcfdU)时表示空闲。

非NCQ模式下link->active_tag=qc->tag;命令完成后置为空闲。

NCQ模式link->active_tag不会被使用。但在libata内部命令中会置为空闲,然而,libata内部根本不会使用NCQ模式。所以NCQ模式下,link->active_tag代表了上一条非NCQ命令的状态

link->sactive; 用于NCQ,0~0xffffffff

bitmask型

NCQ模式表示该link下正活跃的命令,非NCQ模式下link->sactive=0;

qc->hw_tag; 0~31

数值型

表示当前命令使用的真实命令号tag,寄存器操作时都是使用的qc->hw_tag。一般情况qc->hw_tag==qc->tag。

qc->tag; 0~32, 32表示libata内部命令

数值型

libata库使用的tag,与真实的tag有区别,该tag可以=32(ATA_TAG_INTERNAL),表示是内部使用的tag,ATA最多支持32个命令,所以对硬件来说32是非法的,软件层面用32来表示libata内部专用命令

ap->qc_active; 0~0xffffffff

bitmask型

无论是否为NCQ模式,用于表示该ata port下正活跃的命令,

       link->sactive的区别,link->sactive只用于NCQ模式,一个port下可最多16个link,而一个link才代表了一个硬盘,所以判断一个命令是否繁忙是使用link->sactive而非ap->qc_active

1个port的最多16个link的中断都会汇集到该port上,在中断处理函数中ap->qc_active用来对产生中断的tag进行筛选,以保证程序的正常运行。

ap->nr_active_links;

有正在执行的qc的link数量

核心ATA发送命令 ata_qc_issue

所有发送ata的命令都会调用到此接口。

//libata-core.c
0-> ata_qc_issue //分发taskfile到device,taskfile存放于qc中1-> link->sactive |= 1 << qc->hw_tag; //NCQ模式下使用sactive的bit表示待处理的多个命令, 命令完成后清除sactive对应bit。1-> link->active_tag = qc->tag; //非NCQ模式使用link->active_tag表示待处理的单个命令, 命令完成后active_tag=ATA_TAG_POISON表示空闲。1-> ata_sg_setup //如果为DMA模式,则设置sgl2-> dma_map_sg //重新生成sgl表,以减少链表个数,并映射物理地址到io虚拟地址(dma_address)1-> ap->ops->qc_prep(qc); //ahci_qc_prep 将qc命令拷贝到正确的位置,准备发送1-> ap->ops->qc_issue(qc); //ahci_qc_issue 发送qc命令

ahci_qc_prep

命令准备,将qc中的命令移到AHCI规定的Command Header及Command Table中

struct ahci_cmd_hdr {__le32          opts;__le32         status;__le32           tbl_addr;__le32         tbl_addr_hi;__le32          reserved[4];
};
struct ahci_sg {__le32          addr;__le32         addr_hi;__le32          reserved;__le32         flags_size;
};
0-> ahci_qc_prep1-> ata_tf_to_fis //将tf的各个值填入到cfis中对应字段1-> memcpy(cmd_tbl + AHCI_CMD_TBL_CDB, qc->cdb, qc->dev->cdb_len); //如果是scsi的命令,则拷贝cdb命令到ACMD中,即使用SATA中的ATAPI命令。1-> ahci_fill_sg //将上层的sgl中的sg一个一个填入到PRD表中1-> ahci_fill_cmd_slot //填充command header,command header的地址初始化阶段已被写到了PxCLB和PxCLBU寄存器中2-> opts = cmd_fis_len | n_elem << 16 | (qc->dev->link->pmp << 12);opts |= AHCI_CMD_WRITE; //写命令opts |= AHCI_CMD_ATAPI | AHCI_CMD_PREFETCH; //atapi命令DW0=opts2-> 清零PRDBC,该字段用于当前已经完成的读写字节数2-> 将本命令的内存的DMA地址赋值给CTBACommand Header初始化位置:
ahci_port_resume->ahci_start_port->ahci_start_fis_rx->writel(pp->cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR);

如图可知AHCI_CMD_TBL_SZ = 0x80 + 64K * 16,但Linux驱动中没有使用64K个PRD,只使用了168个,所以AHCI_CMD_TBL_SZ = 0x80 + 168 * 16;

ahci_qc_issue

让HBA发送命令

0-> ahci_qc_issue1-> 往SActive(SCR3)寄存器写入tag //如果为NCQ模式则进行此步骤,用于表示该命令待处理,此寄存器通过软件只能由0到1,所以只需对对应bit写1即可,无需读-改-写。1-> FBS模式下配置FBS相关寄存器1-> PxCI.CI对应tag位置1

端口恢复 ahci_port_resume

//初始化时
ahci_host_activate->ata_host_activate->ahci_port_start->ahci_port_resume
//运行时
ata_port_schedule_eh->ata_std_sched_eh->scsi_schedule_eh->scsi_error_handler//SCSI的主错误处理内核态线程, 通过scsi_eh_wakeup唤醒->ata_scsi_error->ata_scsi_port_error_handler->ata_eh_handle_port_resume->ahci_port_resume
//端口恢复
ahci_port_resume-> ahci_power_up->配置PxCMD寄存器,开启Staggered Spin-up(交错启动模式),切换到Active模式-> ahci_start_port-> ahci_start_fis_rx-> 配置PxCLB、PxCLBU、PxFB、PxFBU寄存器-> 配置PxCMD寄存器,使能接收FIS-> ahci_start_engine-> 配置PxCMD寄存器,使能端口DMA引擎-> 配置LED闪烁定时器及回调函数

ahci_stop_engine

int ahci_stop_engine(struct ata_port *ap)
{void __iomem *port_mmio = ahci_port_base(ap);struct ahci_host_priv *hpriv = ap->host->private_data;u32 tmp;......tmp = readl(port_mmio + PORT_CMD);....../* setting HBA to idle */tmp &= ~PORT_CMD_START;writel(tmp, port_mmio + PORT_CMD);/* wait for engine to stop. This could be as long as 500 msec */tmp = ata_wait_register(ap, port_mmio + PORT_CMD,PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500);if (tmp & PORT_CMD_LIST_ON)return -EIO;return 0;
}

ahci_start_engine

void ahci_start_engine(struct ata_port *ap)
{void __iomem *port_mmio = ahci_port_base(ap);u32 tmp;/* start DMA */tmp = readl(port_mmio + PORT_CMD);tmp |= PORT_CMD_START;writel(tmp, port_mmio + PORT_CMD);readl(port_mmio + PORT_CMD); /* flush */
}

ahci_stop_fis_rx

static int ahci_stop_fis_rx(struct ata_port *ap)
{void __iomem *port_mmio = ahci_port_base(ap);u32 tmp;/* disable FIS reception */tmp = readl(port_mmio + PORT_CMD);tmp &= ~PORT_CMD_FIS_RX;writel(tmp, port_mmio + PORT_CMD);/* wait for completion, spec says 500ms, give it 1000 */tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_FIS_ON,PORT_CMD_FIS_ON, 10, 1000);if (tmp & PORT_CMD_FIS_ON)return -EBUSY;return 0;
}

ahci_error_handler

void ahci_error_handler(struct ata_port *ap)
{struct ahci_host_priv *hpriv = ap->host->private_data;if (!(ap->pflags & ATA_PFLAG_FROZEN)) {/* restart engine */hpriv->stop_engine(ap); //ahci_stop_enginehpriv->start_engine(ap);//ahci_start_engine}sata_pmp_error_handler(ap);if (!ata_dev_enabled(ap->link.device))hpriv->stop_engine(ap);
}

sata_pmp_error_handler

热插拔时

.error_handler = ahci_error_handler

-> sata_pmp_error_handler(libata_pmp.c)

->sata_pmp_eh_recover(libata_pmp.c)

->ata_eh_recover(libata_eh.c)

->ata_eh_reset(libata_eh.c)

->.postreset

->..-> ata_std_postreset(libata_core.c)

-> sata_print_link_status(libata_core.c)

void sata_pmp_error_handler(struct ata_port *ap)
{ata_eh_autopsy(ap);ata_eh_report(ap);sata_pmp_eh_recover(ap);ata_eh_finish(ap);
}

ahci_port_suspend

static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg)
{const char *emsg = NULL;int rc;rc = ahci_deinit_port(ap, &emsg);if (rc == 0)ahci_power_down(ap);else {ata_port_err(ap, "%s (%d)\n", emsg, rc);ata_port_freeze(ap);}ahci_rpm_put_port(ap);return rc;
}
static int ahci_deinit_port(struct ata_port *ap, const char **emsg)
{int rc;struct ahci_host_priv *hpriv = ap->host->private_data;/* disable DMA */rc = hpriv->stop_engine(ap);/* disable FIS reception */rc = ahci_stop_fis_rx(ap);return 0;
}

更多AHCI的中断处理:Linux AHCI 驱动(中断部分)_chen_xing_hai的博客-CSDN博客

Linux AHCI驱动相关推荐

  1. linux pcie驱动框架_Linux设备驱动框架设计

    引子 Linux操作系统的一大优势就是支持数以万计的芯片设备,大大小小的芯片厂商工程师都在积极地向Linux kernel提交设备驱动代码.能让这个目标得以实现,这背后隐藏着一个看不见的技术优势:Li ...

  2. linux 设备驱动 probe,Linux驱动模型Probe解惑

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 问题 首先来回顾下,Linux设备驱动模型中bus.device和driver三者的关系:bus是物理总线的抽象. de ...

  3. sata及adma控制器linux操作系统驱动的设计与实现,SATA及ADMA控制器Linux操作系统驱动的设计与实现.pdf...

    SATA及ADMA控制器Linux操作系统驱动的设计与实现.pdf 书书书 第 23 卷第 ll 期 计算机应用与软件VoI 23 No ll 2006 年 ll 月 Computer AppIica ...

  4. Linux音频设备驱动

    在Linux中,先后出现了音频设备的两种框架OSS和ALSA,本节将在介绍数字音频设备及音频设备硬件接口的基础上,展现OSS和ALSA驱动的结构. 17.1-17.2节讲解了音频设备及PCM.IIS和 ...

  5. linux设备驱动第五篇:驱动中的并发与竟态

    目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...

  6. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  7. linux音频驱动dma数据,Linux音频驱动简述

    3.2 mixer接口 int register_sound_mixer(structfile_operations *fops, int dev); 上述函数用于注册1个混音器,第1个参数fops即 ...

  8. Linux驱动无硬件设备,Linux设备驱动与硬件通信

    Linux物理设备驱动,主要有几种类型,如:IO类.内存类.总线类.IO类我们平时接触的最多,其主要特点是,通过IO设备的寄存器操作硬件,具体需要去查看硬件手册. 1. IO端口和IO内存 在硬件层, ...

  9. Linux主机驱动与外设驱动分离思想

    - by 宋宝华(Barry Song) 1主机.外设驱动分离的意义 在Linux设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想.举一个简单的例子,假设我们要通过SPI总线访问某外设,在 ...

最新文章

  1. 枚举 ---- D. Zigzags[ Educational Codeforces Round 94 (Rated for Div. 2)]思维枚举优化4重循环
  2. R语言使用gt包和gtExtras包漂亮地显示表格数据:gtExtras包的gt_hulk_col_numeric函数对单列、多列数据进行着色、使用不同的调色板(color palette)对列着色
  3. 使用Minify合并css和js减少http请求
  4. 中国移动问答 赢取幸运卡标准答案
  5. 第一个c++泛型函数(即模板)
  6. Python解析pdf转为TXT格式
  7. OOP的几个不常用的方法
  8. 从谷歌浏览器复制不带样式_文字特效游戏海报特效字体photoshop字体图层样式
  9. 深度学习——夏侯南溪关注的深度学习任务
  10. Maven的Snapshot版本与Release版本
  11. 在 Less 中写 IE 的css hack
  12. 数值计算详细笔记(二):非线性方程解法
  13. JavaScript初级学习笔记(待完成)
  14. xrd计算晶面间距_xrd如何计算晶格间距(1)
  15. 解决服务器上中文显示乱码问题
  16. XCode 报错Thread 2:signal SIGABRT
  17. Threadlocal学习及内存泄漏原因和解决方案
  18. 资源文件冲突error RC2151 : cannot reuse string constants, 61446(0xF006)
  19. 项目1 设计简易灯箱画廊 实训要求: (1)利用超链接和图像标记设计简易灯箱画廊。 (2)给简易灯箱画廊增加背景音乐效果。
  20. 只需一张自拍,网易伏羲用这种方法直接生成「个人专属」游戏角色

热门文章

  1. NAT网络地址转换技术(三)在防火墙上配置源NAT和NAT Server
  2. Criterion 用法
  3. 根据word自动生成html代码文件
  4. 一个运维工程师必须要知道的(工作职责与应用场景)干货整理
  5. JS实现右键拖动元素
  6. No.059<软考>《(高项)备考大全》【冲刺13】《软考高项极简知识点(2)》
  7. searchsploit 漏洞搜索
  8. Python: self的含义
  9. python修改sheet名称_openpyxl修改sheet名,sheet颜色,删除sheet的方法
  10. 一种相对高效的按键消抖方法