HDIO_ ioctl 调用总结

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[];否则,原始值将写回,保持不变。

  1. 驱动器未能执行命令 (EIO)。

  2. 在 out_flags 中设置了一个或多个位。

  3. 请求的 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 控制器忙