HDIO_ ioctl 调用总结¶
Edward A. Falk <efalk@google.com>
2004 年 11 月
本文档试图描述 HD/IDE 层支持的 ioctl(2) 调用。这些调用主要在 drivers/ata/libata-scsi.c 中实现 (截至 Linux 5.11)。
ioctl 值在 <linux/hdreg.h> 中列出。在编写本文时,它们如下:
将参数指针传递到用户空间的 ioctl
HDIO_GETGEO
获取设备几何信息
HDIO_GET_32BIT
获取当前的 io_32bit 设置
HDIO_GET_IDENTITY
获取 IDE 标识信息
HDIO_DRIVE_TASKFILE
执行原始任务文件
HDIO_DRIVE_TASK
执行任务和特殊驱动器命令
HDIO_DRIVE_CMD
执行特殊驱动器命令
传递非指针值的 ioctl
HDIO_SET_32BIT
更改 io_32bit 标志
以下信息是通过阅读内核源代码确定的。随着时间的推移,可能会进行一些更正。
通用
除非另有说明,否则所有 ioctl 调用在成功时返回 0,在出错时返回 -1 并将 errno 设置为适当的值。
除非另有说明,否则所有 ioctl 调用在尝试将数据复制到用户地址空间或从用户地址空间复制数据失败时返回 -1 并将 errno 设置为 EFAULT。
除非另有说明,否则所有数据结构和常量都在 <linux/hdreg.h> 中定义
- HDIO_GETGEO
获取设备几何信息
用法
struct hd_geometry geom; ioctl(fd, HDIO_GETGEO, &geom);
- 输入
无
- 输出
包含以下内容的 hd_geometry 结构:
磁头数
磁头数
扇区
每磁道的扇区数
柱面
柱面数,模 65536
起始
此分区的起始扇区。
- 错误返回
EINVAL
如果设备不是磁盘驱动器或软盘驱动器,或者用户传递了空指针
- 注意
对于现代磁盘驱动器来说,这并不是特别有用,因为它们的几何结构无论如何都是一个礼貌的虚构。现代驱动器现在完全通过扇区号(LBA 寻址)寻址,并且驱动器几何结构是一个抽象,实际上可能会发生变化。目前(截至 2004 年 11 月),几何值是“bios”值 —— 大概是 Linux 首次启动时驱动器的值。
此外,hd_geometry 的柱面字段是一个无符号短整数,这意味着在大多数架构上,此 ioctl 在具有超过 65535 个磁道的驱动器上不会返回有意义的值。
起始字段是无符号长整数,这意味着它不会包含超过 219 GB 的磁盘的有意义的值。
- HDIO_GET_IDENTITY
获取 IDE 标识信息
用法
unsigned char identity[512]; ioctl(fd, HDIO_GET_IDENTITY, identity);
- 输入
无
- 输出
ATA 驱动器标识信息。有关完整描述,请参阅 ATA 规范中的 IDENTIFY DEVICE 和 IDENTIFY PACKET DEVICE 命令。
- 错误返回
EINVAL 在分区而不是整个磁盘设备上调用
ENOMSG IDENTIFY DEVICE 信息不可用
- 注意
返回在探测驱动器时获得的信息。此信息中的一部分可能会发生更改,并且此 ioctl 不会重新探测驱动器以更新信息。
此信息也可从 /proc/ide/hdX/identify 获取
- HDIO_GET_32BIT
获取当前的 io_32bit 设置
用法
long val; ioctl(fd, HDIO_GET_32BIT, &val);
- 输入
无
- 输出
当前 io_32bit 设置的值
- 注意
0=16 位,1=32 位,2,3 = 32 位 + 同步
- HDIO_DRIVE_TASKFILE
执行原始任务文件
- 注意
如果您手头没有 ANSI ATA 规范的副本,您可能应该忽略此 ioctl。
通过写入驱动器的“任务文件”寄存器直接执行 ATA 磁盘命令。需要 ADMIN 和 RAWIO 访问权限。
用法
struct { ide_task_request_t req_task; u8 outbuf[OUTPUT_SIZE]; u8 inbuf[INPUT_SIZE]; } task; memset(&task.req_task, 0, sizeof(task.req_task)); task.req_task.out_size = sizeof(task.outbuf); task.req_task.in_size = sizeof(task.inbuf); ... ioctl(fd, HDIO_DRIVE_TASKFILE, &task); ...
输入
(有关传递给 ioctl 的内存区域的详细信息,请参阅下文。)
io_ports[8]
要写入任务文件寄存器的值
hob_ports[8]
高位字节,用于扩展命令。
out_flags
指示哪些寄存器有效的标志
in_flags
指示应返回哪些寄存器的标志
data_phase
见下文
req_cmd
要执行的命令类型
out_size
输出缓冲区的大小
outbuf
要传输到磁盘的数据缓冲区
inbuf
要从磁盘接收的数据缓冲区(请参阅 [1])
输出
io_ports[]
在任务文件寄存器中返回的值
hob_ports[]
高位字节,用于扩展命令。
out_flags
指示哪些寄存器有效的标志(请参阅 [2])
in_flags
指示应返回哪些寄存器的标志
outbuf
要传输到磁盘的数据缓冲区(请参阅 [1])
inbuf
要从磁盘接收的数据缓冲区
- 错误返回
EACCES 未设置 CAP_SYS_ADMIN 或 CAP_SYS_RAWIO 权限。
ENOMSG 设备不是磁盘驱动器。
ENOMEM 无法为任务分配内存
EFAULT req_cmd == TASKFILE_IN_OUT(截至 2.6.8 尚未实现)
EPERM
req_cmd == TASKFILE_MULTI_OUT 且驱动器多计数尚未设置。
EIO 驱动器未能执行命令。
注意
[1] 仔细阅读以下注意事项。此 ioctl 充满了陷阱。使用此 ioctl 时应格外小心。错误很容易损坏数据或使系统挂起。
[2] 输入和输出缓冲区都从用户复制,并写回用户,即使未使用也是如此。
[3] 如果在 out_flags 中设置了一个或多个位,并且 in_flags 为零,则以下值用于 in_flags.all,并在完成时写回 in_flags。
如果为驱动器启用了 LBA48 寻址,则为 IDE_TASKFILE_STD_IN_FLAGS | (IDE_HOB_STD_IN_FLAGS << 8)
如果是 CHS/LBA28,则为 IDE_TASKFILE_STD_IN_FLAGS
in_flags.all 和每个启用位字段之间的关联取决于字节序;幸运的是,TASKFILE 仅使用 inflags.b.data 位并忽略所有其他位。最终结果是,在任何字节序的机器上,它除了在完成时修改 in_flags 外,没有其他作用。
[4] SELECT 的默认值为 (0xa0|DEV_bit|LBA_bit),但每个端口芯片组有四个驱动器除外。对于每个端口芯片组有四个驱动器的情况,第一对为 (0xa0|DEV_bit|LBA_bit),第二对为 (0x80|DEV_bit|LBA_bit)。
[5] ioctl 的参数是指向包含 ide_task_request_t 结构的内存区域的指针,后跟一个可选的要传输到驱动器的数据缓冲区,后跟一个可选的要从驱动器接收的数据缓冲区。
命令通过 ide_task_request_t 结构传递给磁盘驱动器,该结构包含以下字段:
io_ports[8]
任务文件寄存器的值
hob_ports[8]
高位字节,用于扩展命令
out_flags
指示 io_ports[] 和 hob_ports[] 数组中哪些条目包含有效值的标志。类型为 ide_reg_valid_t。
in_flags
指示 io_ports[] 和 hob_ports[] 数组中哪些条目预计在返回时包含有效值的标志。
data_phase
见下文
req_cmd
命令类型,见下文
out_size
输出(用户 -> 驱动器)缓冲区大小,字节
in_size
输入(驱动器 -> 用户)缓冲区大小,字节
当 out_flags 为零时,将加载以下寄存器。
HOB_FEATURE
如果驱动器支持 LBA48
HOB_NSECTOR
如果驱动器支持 LBA48
HOB_SECTOR
如果驱动器支持 LBA48
HOB_LCYL
如果驱动器支持 LBA48
HOB_HCYL
如果驱动器支持 LBA48
FEATURE
NSECTOR
SECTOR
LCYL
HCYL
SELECT
首先,如果 LBA48,则用 0xE0 屏蔽,否则用 0xEF 屏蔽;然后,与 SELECT 的默认值进行或运算。
如果设置了 out_flags 中的任何位,则会加载以下寄存器。
HOB_DATA
如果设置了 out_flags.b.data。HOB_DATA 将在小端机器上的 DD8-DD15 和大端机器上的 DD0-DD7 上传输。
DATA
如果设置了 out_flags.b.data。DATA 将在小端机器上的 DD0-DD7 和大端机器上的 DD8-DD15 上传输。
HOB_NSECTOR
如果设置了 out_flags.b.nsector_hob
HOB_SECTOR
如果设置了 out_flags.b.sector_hob
HOB_LCYL
如果设置了 out_flags.b.lcyl_hob
HOB_HCYL
如果设置了 out_flags.b.hcyl_hob
FEATURE
如果设置了 out_flags.b.feature
NSECTOR
如果设置了 out_flags.b.nsector
SECTOR
如果设置了 out_flags.b.sector
LCYL
如果设置了 out_flags.b.lcyl
HCYL
如果设置了 out_flags.b.hcyl
SELECT
与 SELECT 的默认值进行或运算,并且无论 out_flags.b.select 如何都会加载。
如果满足以下条件之一,则在命令完成后将任务文件寄存器从驱动器读回到 {io|hob}_ports[];否则,原始值将写回,保持不变。
驱动器未能执行命令 (EIO)。
在 out_flags 中设置了一个或多个位。
请求的 data_phase 为 TASKFILE_NO_DATA。
HOB_DATA
如果设置了 in_flags.b.data。它将包含小端机器上的 DD8-DD15 和大端机器上的 DD0-DD7。
DATA
如果设置了 in_flags.b.data。它将包含小端机器上的 DD0-DD7 和大端机器上的 DD8-DD15。
HOB_FEATURE
如果驱动器支持 LBA48
HOB_NSECTOR
如果驱动器支持 LBA48
HOB_SECTOR
如果驱动器支持 LBA48
HOB_LCYL
如果驱动器支持 LBA48
HOB_HCYL
如果驱动器支持 LBA48
NSECTOR
SECTOR
LCYL
HCYL
data_phase 字段描述要执行的数据传输。值是以下之一:
TASKFILE_IN
TASKFILE_MULTI_IN
TASKFILE_OUT
TASKFILE_MULTI_OUT
TASKFILE_IN_OUT
TASKFILE_IN_DMA
TASKFILE_IN_DMAQ
== IN_DMA(不支持排队)
TASKFILE_OUT_DMA
TASKFILE_OUT_DMAQ
== OUT_DMA(不支持排队)
TASKFILE_P_IN
未实现
TASKFILE_P_IN_DMA
未实现
TASKFILE_P_IN_DMAQ
未实现
TASKFILE_P_OUT
未实现
TASKFILE_P_OUT_DMA
未实现
TASKFILE_P_OUT_DMAQ
未实现
req_cmd 字段对命令类型进行分类。它可以是以下之一:
IDE_DRIVE_TASK_NO_DATA
IDE_DRIVE_TASK_SET_XFER
未实现
IDE_DRIVE_TASK_IN
IDE_DRIVE_TASK_OUT
未实现
IDE_DRIVE_TASK_RAW_WRITE
[6] 不要访问 {in|out}_flags->all,除非重置所有位。始终访问各个位字段。->all 值将取决于字节序而翻转。出于同样的原因,不要使用 hdreg.h 中定义的 IDE_{TASKFILE|HOB}_STD_{OUT|IN}_FLAGS 常量。
- HDIO_DRIVE_CMD
执行特殊驱动器命令
注意:如果您手头没有 ANSI ATA 规范的副本,您可能应该忽略此 ioctl。
用法
u8 args[4+XFER_SIZE]; ... ioctl(fd, HDIO_DRIVE_CMD, args);
- 输入
WIN_SMART 之外的命令
args[0]
COMMAND
args[1]
NSECTOR
args[2]
FEATURE
args[3]
NSECTOR
WIN_SMART
args[0]
COMMAND
args[1]
SECTOR
args[2]
FEATURE
args[3]
NSECTOR
- 输出
args[] 缓冲区填充了寄存器值,后跟任何
磁盘返回的数据。
args[0]
状态
args[1]
错误
args[2]
NSECTOR
args[3]
未定义
args[4+]
该命令返回的 NSECTOR * 512 字节的数据。
- 错误返回
EACCES 访问被拒绝:需要 CAP_SYS_RAWIO
ENOMEM 无法为任务分配内存
EIO 驱动器报告错误
注意
[1] 对于 WIN_SMART 之外的命令,args[1] 应该等于 args[3]。SECTOR、LCYL 和 HCYL 未定义。对于 WIN_SMART,0x4f 和 0xc2 分别加载到 LCYL 和 HCYL 中。在这两种情况下,SELECT 都将包含驱动器的默认值。有关 SELECT 的默认值,请参阅 HDIO_DRIVE_TASKFILE 注释。
[2] 如果 NSECTOR 值大于零,并且驱动器在中断命令时设置了 DRQ,则会从设备读取 NSECTOR * 512 字节的数据到 NSECTOR 之后的区域。在上面的例子中,该区域将是 args[4..4+XFER_SIZE]。无论 HDIO_SET_32BIT 设置如何,都会使用 16 位 PIO。
[3] 如果 COMMAND == WIN_SETFEATURES && FEATURE == SETFEATURES_XFER && NSECTOR >= XFER_SW_DMA_0 && 驱动器支持任何 DMA 模式,IDE 驱动程序将尝试相应地调整驱动器的传输模式。
- HDIO_DRIVE_TASK
执行任务和特殊驱动器命令
注意:如果您手头没有 ANSI ATA 规范的副本,您可能应该忽略此 ioctl。
用法
u8 args[7]; ... ioctl(fd, HDIO_DRIVE_TASK, args);
- 输入
任务文件寄存器值
args[0]
COMMAND
args[1]
FEATURE
args[2]
NSECTOR
args[3]
SECTOR
args[4]
LCYL
args[5]
HCYL
args[6]
SELECT
- 输出
任务文件寄存器值
args[0]
状态
args[1]
错误
args[2]
NSECTOR
args[3]
SECTOR
args[4]
LCYL
args[5]
HCYL
args[6]
SELECT
- 错误返回
EACCES 访问被拒绝:需要 CAP_SYS_RAWIO
ENOMEM 无法为任务分配内存
ENOMSG 设备不是磁盘驱动器。
EIO 驱动器未能执行命令。
注意
[1] 忽略 SELECT 寄存器的 DEV 位 (0x10),并使用驱动器的适当值。所有其他位保持不变。
- HDIO_SET_32BIT
更改 io_32bit 标志
用法
int val; ioctl(fd, HDIO_SET_32BIT, val);
- 输入
io_32bit 标志的新值
- 输出
无
- 错误返回
EINVAL 在分区而不是整个磁盘设备上调用
EACCES 访问被拒绝:需要 CAP_SYS_ADMIN 权限
EINVAL 值超出范围 [0 3]
EBUSY 控制器忙