libata 开发手册

第1章。介绍 
libata是Linux内核中,用来支持ATA主机控制器和设备的一个库。
libata提供了一个ATA驱动API,ATA和ATAPI设备类型传输,ATA设备SCSI与ATA转换(T10 SAT标准)。 
本指南描述了libata驱动的API,库函数,库内部,和一对简单的ATA低层驱动程序。

第2章。 libata驱动API
每一个低层libata硬件驱动定义一个struct ata_port_operation, 它为ATA或SCSI提供低层驱动接口的集合。
基于帧信息结构的驱动通过->qc_prep()和->qc_issue()高级别挂钩挂接到系统.

struct ata_port_operations {
        //关闭ATA端口
		void (*port_disable) (struct ata_port *); //关闭ATA端口,ata_port_disable()
		 
		 //配置设备
        void (*dev_config) (struct ata_port *, struct ata_device *); //发送IDENTIFY标识后,配置设备
			
		//Set PIO/DMA 模式
        void (*set_piomode) (struct ata_port *, struct ata_device *); //设置dev->pio_mode
        void (*set_dmamode) (struct ata_port *, struct ata_device *); //设置dev->dma_mode
		void (*post_set_mode) (struct ata_port *ap);//SET FEATURES-XFER MODE命令成功完成后调用
		unsigned int (*mode_filter) (struct ata_port *, struct ata_device *, unsigned int);
		
		//taskfile读写
        void (*sff_tf_load) (struct ata_port *ap, const struct ata_taskfile *tf);//写,ata_tf_load()
        void (*sff_tf_read) (struct ata_port *ap, struct ata_taskfile *tf); //读,ata_tf_read();
		
		//PIO 数所读写
		void (*sff_data_xfer) (struct ata_device *, unsigned char *, unsigned int, int);
			 bmdma风格的驱动必顺实现,PIO数据拷贝。
					ata_sff_data_xfer_noirq()或ata_sff_data_xfer()或ata_sff_data_xfer32().
		 //ata命令执行
        void (*sff_exec_command)(struct ata_port *ap, const struct ata_taskfile *tf); 		
													//ATA命令执行,ata_sff_exec_command();
		 //读特定的ATA 隐藏寄存器
        u8   (*sff_check_status)(struct ata_port *ap);		//读Status状态寄存器,通常为ata_check_status();
        u8   (*sff_check_altstatus)(struct ata_port *ap);	//读AltStatus寄存器 
        //u8	  (*sff_check_err)(struct ata_port*ap);			//读Error寄存器
		 //写特定的ATA 隐藏寄存器
		void (*sff_devctl)(struct ata_port*ap,u8 ctl);
		
		 //在总线上选择ATA设备
		void (*sff_dev_select)(struct ata_port *ap, unsigned int device);
		
		 //复位ATA总线
        void (*phy_reset) (struct ata_port *ap);
         
		 //每个ATAPI DMA 命令过滤
        int (*check_atapi_dma) (struct ata_queued_cmd *qc);//检查是否支持ATAPI DMA分组命令
 
		 //PCI IDE BMDMA引擎控制
        void (*bmdma_setup) (struct ata_queued_cmd *qc); //设置
        void (*bmdma_start) (struct ata_queued_cmd *qc); //开始
		void (*bmdma_stop) (struct ata_queued_cmd *qc);	//停止
        u8   (*bmdma_status) (struct ata_port *ap);//读状态
        
		 //上层任务(taskfile)挂载点
		void (*qc_prep) (struct ata_queued_cmd *qc); //缓存区进行DMA映射(SG表)后调用
        int (*qc_issue) (struct ata_queued_cmd *qc);//发送一个命令
		
		 //错误处理
        void (*eng_timeout) (struct ata_port *ap);
		void (*phy_reset)(struct ata_port*ap);
		void (*error_handler) (struct ata_port *ap);
		void (*freeze) (struct ata_port *ap);
		void (*thaw) (struct ata_port *ap);
	
		 //硬件中断处理	
        irqreturn_t (*irq_handler)(int, void *, struct pt_regs *);
        void (*irq_clear) (struct ata_port *);
 
		 //SATA phy 读写
        u32 (*scr_read) (struct ata_port *ap, unsigned int sc_reg,u32*val);//读标准的SATA phy寄存器
        void (*scr_write) (struct ata_port *ap, unsigned int sc_reg,u32 val); //写标准的SATA phy寄存器
 
		 //初始化和关闭
        int (*port_start) (struct ata_port *ap);
        void (*port_stop) (struct ata_port *ap);
        void (*host_stop) (struct ata_host_set *host_set);
 
        
};
禁止ATA端口:void (*port_disable) (struct ata_port *);
		由ata_bus_probe()或ata_bus_reset()错误处理调用,或者是从SCSI模块中注销,热挺拨.
	或者由ata_scsi_release()调用;通常挂钩为ata_port_disable();

设备配置:void(*dev_config)(struct ata_port*,struct ata_device*);
		对发现的每个设备发送IDENTIFY包后(ata_dev_identify())调用,由ata_device_add()调用.此函数可以为NULL.
	通常用于相关设备的早期修复,发送SET FEATURES-XFER MODE,和早期操作.

读/写任务(taskfile):
		void(*tf_load)(struct ata_port*ap,struct ata_taskfile*tf);
		写任务,加载给定的taskfile到硬件的寄存器/DMA缓存。
		通常为ata_tf_load();
		void(*tf_read)(struct ata_port*ap,struct ata_taskfile*tf);
		读,读硬件寄存器/DMA缓存,获取当前设置的任务(taskfile)寄存器对应的值.
		通常为ata_tf_read();

ATA命令执行:void(*exec_command)(struct ata_port*,struct ata_taskfile*tf);
		执行一个ATA命令,先通过->tf_load()初始化设备。通常为ata_exec_command();

ATAPI DMA功能命令过滤:
		void(*check_atapi_dma)(struct ata_queued_cmd*qc);
		允许低层驱动去过滤ATA分组命令,返回一个状态表明是否支持ATAPI DMA分组命令。为NULL是为支持。
		
读特定的ATA 隐藏寄存器:		
		u8 (*check_status)(struct ata_port *ap);
		u8 (*check_altstatus)(struct ata_port *ap);
		u8 (*check_err)(struct ata_port *ap);
		读取硬件的ATA隐藏寄存器Status/AltStatus/Error,有某些硬件中,读状态寄存器具有清除中断状态的副作用。
			通常为ata_check_status();请注意,因为被ata_device_add()调用,故至少是一个虚拟函数
			提供给所有的驱动程序,用于清除设备中断,即使控制器实际上并没有一个taskfile状态寄存器。

在总线上选择ATA设备:
		void (*dev_select)(struct ata_port *ap, unsigned int device);
	发送低层的硬件指令,导致某个硬件设备, 在ATA总线上视为“选中”(激活和可供使用)。
	这通常对基于帧信息结构(FIS)的设备没有意义。 
	大多数基于taskfile的硬件驱动程序使用ata_std_dev_select()这个钩子。
	不支持多个端口的控制器(如SATA contollers)将使用ata_noop_dev_select()。

复位ATA总线:
         void (*phy_reset) (struct ata_port *ap);
		 在探测阶段的第一步。操作因总线类型而有所不同, 
		在唤醒设备和探测设备存在后(PATA和SATA),通常是一个软复位(SRST)将被执行。
		驱动程序通常使用ata_bus_reset()或sata_phy_reset()。
		许多SATA驱动使用sata_phy_reset()或者从自己的phy_reset()函数调用它。

PCI IDE BMDMA引擎控制: (IDE驱动使用)
        void (*bmdma_setup) (struct ata_queued_cmd *qc); //设置
        void (*bmdma_start) (struct ata_queued_cmd *qc); //开始
		void (*bmdma_stop) (struct ata_queued_cmd *qc);	//停止
        u8   (*bmdma_status) (struct ata_port *ap);//读状态
		当进行IDE BMDMA传输时,设置(->bmdma_setup),开始(->bmdma_start),停止(->bmdma_stop)硬件的DMA引擎。
		->bmdma_status用来读取标准的PCI IDE DMA状态寄存器。对基于帧信息(FIS)的设备驱动通常为NULL。
		传统的IDE设备使用:ata_bmdma_setup(), ata_bmdma_start(),  ata_bmdma_stop(), ata_bmdma_status().
		
上层任务(taskfile)挂载点:
		void (*qc_prep) (struct ata_queued_cmd *qc);
        int (*qc_issue) (struct ata_queued_cmd *qc);
		这两个钩子函数都支持几个以上taskfile/DMA引擎;
  		当缓冲区已经DMA映射(填充硬件的DMA分散聚集表)后 ->qc_prep被调用。大多数驱动使用ata_qc_prep(),
			但有些高级驱动使用自己的函数;
		一但硬件或S/G表准备好后,->qc_issue()用来发送一个命令。高级驱动实现自己的函数。
		IDE BMDMA驱动使用ata_qc_issue_prot()对任务基于协义分发调度。

超时错误外理:
        void (*eng_timeout) (struct ata_port *ap);
		上层错误处理函数。当一个命令超时时,由错误处理线程调用。
		IDE BMDMA驱动可能使用ata_eng_timeout();
		许多新的硬件会实现自己的错误处理函数。
		
硬件中断处理:	
         irqreturn_t (*irq_handler)(int, void *, struct pt_regs *);
				由libata向系统注册的中断处理函数。第二个参数,设备实例,应转换为struct ata_host_set.
			传统的IDE驱动程序使用ata_interrupt(),它在host_set上扫描所有端口,查找活动的命令队列, 
			并调用ata_host_intr(AP,QC).
         void (*irq_clear) (struct ata_port *);
		 在中断处理函数注册一前,关闭中断。确保硬件安静。
		 大多传统的IDE驱动使用ata_bmdma_irq_clear();它只是清除DMA状态寄存器中的中断和错误标志.
		 
SATA phy 读写:
         u32 (*scr_read) (struct ata_port *ap, unsigned int sc_reg);
         void (*scr_write) (struct ata_port *ap, unsigned int sc_reg,u32 val);
		 读写标准的SATA phy寄存器,目前仅用于sata_phy_reset();
		 sc_reg值有:SCR_STATUS,SCR_CONTROL,SCR_ERROR,SCR_ACTIVE.
		 
初始化和关闭:
        int (*port_start) (struct ata_port *ap);
		每个端口的数据结构初始化完成后调用->port_start().通常这是用来分配每个端口的DMA缓冲区/表/圈,
		使能DMA引擎,以及类似的任务。有些驱动程序也把它作为一个时机来分配驱动私用内存ap->private_data。 
		许多驱动使用ata_port_start()[传统的IDE 还分配PRD表]或者自己实现。
        
		void (*port_stop) (struct ata_port *ap);
		在host_stop后调用,它惟一的功能是释放DMA/内存资源,现在不再使用。
		许多驱动还要释放端口私用数据,ata_port_stop(),释放PRD表。
        void (*host_stop) (struct ata_host_set *host_set);
		在所有的->port_stop()完成后调用。必需完成硬件关机,释放DMA和其它资源.它可能为NULL。
		

第3章。错误处理	
	本章介绍的是libata下的错误处理。
	建议读者阅读SCSI 错误处理(Documentation/SCSI/scsi_eh.txt)和ATA 异常文档。
	
命令的起源: 
	在libata中,一个命令使用struct ata_queued_cmd *qc表示。 
	qc是在端口初始化时预分配的,命令执行会重复使用它。 
	目前为每个端口只分配一种QC,但尚未合并的NCQ分支为每个标志分配一个QC,并与NCQ标签1对1映射。 
	libata的命令有两个来源: libata本身和SCSI中间层。 
	libata的内部命令用于初始化和错误处理。所有正常的BLK请求和SCSI仿真命令均当作SCSI 命令传输,
	并通过回调SCSI host template 的queuecommand函数去执行。

提交命令:
	libata内部命令:
		首先,使用ata_qc_new_init()分配和初始化QC。
		当QC无法使用时,ata_qc_new_init()没有实现任何等待或重试机制,
		因为目前只有在初始化和错误恢复时提交内部命令,所以不能保证其他命令能激活和分配成功。
		一旦已分配的QC taskfile被初始化为要执行的命令。 
		当前QC有两种机制来通知完成。一个是回调 qc->complete_fn(),一个是完成变量qc->waiting.
		qc->complete_fn()是异步的,用于正常的SCSI转换命令。
		qc->waiting是同步的,用于libata内部命令。
		一是初始化完成,就获取host_set锁并提交qc命令.
	SCSI命令:
		所有的libata驱动使用ata_scsi_queuecmd()实例化hostt->queuecommand回调函数。
		scmds可以是仿真的或者是转换的。在处理仿真的scmd时不用qc参与。
		对于转换的scmd,先调用ata_qc_new_init()分配一个qc,再把scmd转换到qc.
		scsi中间层的完成通知函数存储在qc->scsidone().
		qc->complete_fn()回调函数用于完成通知。
		ATA命令使用ata_scsi_qc_complete(),ATAPI命令使用atapi_qc_complete(),
		但均通过qc->scsidone()去通知上层命令的完成。
		当scmd转换成qc后,调用ata_qc_issue()提交qc.
		(注:scsi中间层在调用hostt->queuecommand时是持有host_set锁的,所以所有上述操作均在持有锁host_set下进行)
	
命令处理:
	命令的处理方式根据使用的协议和控制器的不同而不同。 
	为了讨论,假设有一个控制器,它使用taskfile接口和所有标准的回调函数。
	目前有6种 ATA命令协议在使用。根据处理方式可以被分类为以下四类:
		ATA NO DATA or DMA 
				ATA_PROT_NODATA和ATA_PROT_DMA属于这一类。 
				这些类型的命令一但提交就不需要任何软件干预。设备在完成是产生一个中断。 
		ATA PIO 
				ATA_PROT_PIO是这一类。libata目前用轮询来实现PIO。 
				ATA_NIEN位被设置来关闭中断,pio_task在ata_wq上进行轮询和IO。
		ATAPI NODATA or DMA 
				ATA_PROT_ATAPI_NODATA和ATA_PROT_ATAPI_DMA属于此类。 
				packet_task在发出PACKET命令后用来轮询BSY位。 
				一旦设备关闭了BSY,packet_task就传输CDB,处理器开始准备中断处理。 
		ATAPI PIO 
			ATA_PROT_ATAPI是这一类。 
			ATA_NIEN位被置位,类似于ATAPI NODATA or DMA,packet_task提交CDB。
			然后,进一步处理(数据传输)被移交给pio_task。
			
命令完成:
	一但命令提交,所有的qc要么执行完成ata_qc_complete(),要么超时。
	对于要中断处理的命令,ata_host_intr()调用ata_qc_complete().对于PIO task,pio_task调用ata_qc_complete();
	在错误的情况下,packet_task还可能完成命令。
	ata_qc_complete()处理流程:
		1,unmapped DMA 内存。
		2,清qc->flags标志位ATA_QCFLAG_ACTIVE。
		3,调用qc->complete_fn()回调函数,如果返加非0,完成简短的绕行,ata_qc_complete()返回。
		4,调用__ata_qc_complete(),
			a,qc->flag标志清0
			b,ap->active_tag和qc->tag标记
			c,qc->waiting清0,并完成。
			d,清除ap->qactive合适的位,释放QC.
			所以,它基本上会通知上层并且释放qc,只有第3步中异常时完成简短的绕行,被atapi_qc_complete().
		对于所有非ATAPI命令,无论失败与否,几乎相同的代码路径和很少的错误处理。qc成功完成返加success状态,
		否则failed状态。然而,失败的ATAPI命令需要获得检测数据REQUEST SENSE进行更多的处理。 
		如果ATAPI命令失败,带错误状态调用ata_qc_complete(),它又通过qc->complete_fn调用atapi_qc_complete(),
		atapi_qc_complete()设置scmd->result为SAM_STAT_CHECK_CONDITION,并完成scmd并返回1.
		当sense data是为空但scmd->result是CHECK CONDITION就,SCSI中间层会为此scmd调用错误处理,
		并返回1.使得ata_qc_complete()返回没有释放的qc.
		ata_scsi_error()说明qc部分完成.

ata_scsi_error():
		当前libata的错误处理函数hostt->eh_strategy_hendler()是ata_scsi_error()。
		正如上面说到的,有两种情况:超时,ATAPI错误完成。
		这个函数会调用低层的libata驱动回调函数eng_timeout(),标准回调是ata_eng_timeout().
		它会检查qc是否active,如果是会调用ata_qc_timeout(),进行实际的错误处理。
		如果是超时错误进入EH,ata_qc_timeout()停止BMDMA,并且完成QC.注意,现在在EH中,不能调用scsi_done(),
		像SCSI EH文档说的那样,恢复scmd只能是重试scsi_queue_insert()或完成scsi_finish_command().
		在这时,我们把qc->scsidone实例为scsi_finish_command(),并调用ata_qc_complete().
		如果是ATAPI qc错误进入EH,这里的QC已经完成,但没有释放。这样半完成(half-complete)的目的是把qc
		作为占位符,使EH进入到这里。这有点生硬,但是它能工作。一但进入此,调用__ata_qc_complete()明确的释放qc.
		然后,提交内部REQUEST SENSE命令qc,一但获取sense data,直接调用scsi_finish_command()去完成scmd.
		注意,因为我们已经完成并释放了与qc关联的scmd,故不需要再一次调用ata_qc_complete().
		
当前EH的问题:
		a.错误表示太粗糙。目前所有错误情况都由ATA STATUS和ERROR寄存器表示。
		不属于ATA设备错误的错误通过设置ATA_ERR位当作ATA设备的错误。
		需要更好的可以正确地表示ATA和其它error/exceptions的错误描述符
		b.当处理超时,没有任何动作,可以以使设备忽略超时命令,为新命令作好准备。
		c.EH处理ata_scsi_error()没有合适的保护正常的命令处理,一但进入EH,设备就是非静止状态,
		超时命令任何时候都有可以成功或失败,pio_task和atapi_task还在运行。
		d.错误恢复太弱,设备/控制器引起的不匹配错误和其它错误通常需要复位以恢复到已知状态。
		此外,为了支持NCQ或hotplug需要更高级的错误处理。
		e.ATA错误由中断处理例程直接处理,PIO错误由pio_task处理.
		
		对于高级的错误处理来说这是有问题的:
		1,首先,先进的错误处理往往需要上下文和执行内部qc。
		2,即使一个简单的失败(例如CRC错误)信息需求收集,而且还可能引发复杂的错误处理(reset,reconfig) 。
 		有多个代码路径来收集信息,进入EH和触发动作是很痛苦的。
		3,分散的EH代码使得底层驱动实现相当困难。底层驱动重载libata回调函数。
		如果EH分散在几个地方,每个受影响的回调应该完成自己的错误处理部分。这很容易出错和相当痛苦。


第四章及以后:库函数介绍。 参考: https://www.kernel.org/doc/htmldocs/libata/


附:

ata同时提供DMA和PIO两种方式传送信号.
ATA传输方式:

pio:pio传输可以分为PIO寄存器传输和PIO数据传输。PIO寄存器传输主要用于对ATA设备中的寄存器进行读写。读写的数据 位数为8位DD[7:0]。ATA主机控制器根据所要读写的寄存器地址 设置CS0_、CS1_、DA[2:0]地址信号,同时将DIOW_或DIOR_设为有效,ATA主机控制器或ATA设备驱动数据总线释放数据。当DIOW_或DIOR_撤销时,ATA主机控制器或ATA设备从数据总线上读取数据。对于PIO数据传输,所读写的地址为数据端口, 读写数据为16位。

mdadm:mdma传输用于数据传输。ATA主机 控制器向ATA设 备下达MDMA传 输命令后,等待设备向主机发送DMARQ数据传输请求信号。当主机收到DMARQ信号后,向设备发送DMACK_响应信号。MDMA数据传输过程与PIO方式大致相同,也是通过DIOW_或DIOR_的周期变化来控制数据的传输。在数据传输过程中,DMARQ和DMACK_握手信号一直保持有效。
udma:udma传输用于数据传输,比MDADM要快。


你可能感兴趣的:(编程知识点)