ATA 磁盘驱动程序“跳过”内存块

Posted

技术标签:

【中文标题】ATA 磁盘驱动程序“跳过”内存块【英文标题】:ATA disk driver "skipping" chunk of memory 【发布时间】:2021-04-12 13:36:22 【问题描述】:

我正在尝试编写一个简单的 32 位 x86 操作系统,但我的 ATA 磁盘驱动程序代码有问题。

我编写了以下 C++ 类,其函数 read_sects_pio28 应读取 sect_count 磁盘扇区,从偏移量 sect_offs 开始,到物理地址 pa_dest

template<typename T>
void out(uint16_t port, T data)

  asm ("out %0,%1"
       : : "a" (data), "d" (port));


template<typename T>
T in(uint16_t port)

  T data;
  asm volatile("in %1,%0"
               : "=a" (data)
               : "d" (port));
  return data;


template<typename T>
void ins(uint16_t port, uint32_t dest, uint32_t count)

  static_assert(detail::is_uint_le32_t<T>());

  asm volatile("rep ins%z2"
               : "+D" (dest), "+c" (count), "=m" (dest)
               : "d" (port)
               : "memory");


class Disk_driver_ata

  enum : uint32_t
  
    ATA_PORT_DATA = 0x1F0,
    ATA_PORT_SECT_COUNT = 0x1F2,
    ATA_PORT_LBA_LO = 0x1F3,
    ATA_PORT_LBA_MID = 0x1F4,
    ATA_PORT_LBA_HI = 0x1F5,
    ATA_PORT_DRIVE_HEAD = 0x1F6,
    ATA_PORT_STATUS = 0x1F7,
    ATA_PORT_CMD = 0x1F7
  ;

  enum : uint8_t
  
    ATA_PIO48_MASTER = 0x40,
    ATA_PIO48_CMD_READ = 0x24,
    ATA_STATUS_DRQ = 0x08
  ;

public:
  void read_sects(uint32_t pa_dest,
                  uint64_t sect_offs,
                  uint16_t sect_count) const override
   read_sects_pio48(pa_dest, sect_offs, sect_count); 

private:
  static void read_sects_pio48(uint32_t pa_dest,
                               uint64_t sect_offs,
                               uint16_t sect_count)
  
    x86::outb(ATA_PORT_DRIVE_HEAD, ATA_PIO48_MASTER);

    x86::outb(ATA_PORT_SECT_COUNT, (sect_count >> 8) & 0xFF);
    x86::outb(ATA_PORT_LBA_LO, (sect_offs >> 24) & 0xFF);
    x86::outb(ATA_PORT_LBA_MID, (sect_offs >> 32) & 0xFF);
    x86::outb(ATA_PORT_LBA_HI, (sect_offs >> 40) & 0xFF);
    x86::outb(ATA_PORT_SECT_COUNT, sect_count & 0xFF);
    x86::outb(ATA_PORT_LBA_LO, sect_offs & 0xFF);
    x86::outb(ATA_PORT_LBA_MID, (sect_offs >> 8) & 0xFF);
    x86::outb(ATA_PORT_LBA_HI, (sect_offs >> 16) & 0xFF);

    x86::outb(ATA_PORT_CMD, ATA_PIO48_CMD_READ);

    for (uint8_t sec = 0u; sec < sect_count; ++sec) 
      poll_status(ATA_STATUS_DRQ);

      x86::ins<uint32_t>(ATA_PORT_DATA, pa_dest, DISK_SECT_SIZE / 4);
      pa_dest += DISK_SECT_SIZE / 4;
    
  

  static void poll_status(uint8_t status)
  
    while (!(x86::in<uint8_t>(ATA_PORT_STATUS) & status))
      ;
  
;

我想用这个函数来加载我的内核的可执行ELF段,目前只有688字节大,地址0x100000。在磁盘上,该段从0x1800 开始,因此read_sects_pio28pa_dest=0x10000sect_offs=12sect_count=2 调用(我已经使用调试器验证了这实际上是这种情况)。

但这似乎只有某种有效。以下是该段在磁盘上的样子:

00001800  e9 00 00 00 00 55 89 e5  83 ec 08 83 ec 0c 6a 68  |.....U........jh|
00001810  e8 31 02 00 00 83 c4 10  83 ec 0c 6a 65 e8 24 02  |.1.........je.$.|
00001820  00 00 83 c4 10 83 ec 0c  6a 6c e8 17 02 00 00 83  |........jl......|
00001830  c4 10 83 ec 0c 6a 6c e8  0a 02 00 00 83 c4 10 83  |.....jl.........|
00001840  ec 0c 6a 6f e8 fd 01 00  00 83 c4 10 83 ec 0c 6a  |..jo...........j|
00001850  0a e8 f0 01 00 00 83 c4  10 90 c9 c3 55 89 e5 83  |............U...|
00001860  ec 18 8b 45 08 66 89 45  f4 0f b7 45 f4 83 ec 0c  |...E.f.E...E....|
00001870  50 e8 fa 01 00 00 83 c4  10 c9 c3 55 89 e5 83 ec  |P..........U....|
00001880  18 8b 45 08 8b 55 0c 66  89 45 f4 89 d0 88 45 f0  |..E..U.f.E....E.|
00001890  0f b6 55 f0 0f b7 45 f4  83 ec 08 52 50 e8 eb 01  |..U...E....RP...|
000018a0  00 00 83 c4 10 90 c9 c3  55 89 e5 83 ec 18 c7 45  |........U......E|
000018b0  f4 00 00 00 00 83 ec 08  6a 0e 68 d4 03 00 00 e8  |........j.h.....|
000018c0  b7 ff ff ff 83 c4 10 83  ec 0c 68 d5 03 00 00 e8  |..........h.....|
000018d0  88 ff ff ff 83 c4 10 0f  b6 c0 c1 e0 08 89 45 f4  |..............E.|
000018e0  83 ec 08 6a 0f 68 d4 03  00 00 e8 8c ff ff ff 83  |...j.h..........|
000018f0  c4 10 83 ec 0c 68 d5 03  00 00 e8 5d ff ff ff 83  |.....h.....]....|
00001900  c4 10 0f b6 c0 09 45 f4  8b 45 08 c7 00 00 80 0b  |......E..E......|
00001910  00 8b 45 f4 89 c2 8b 45  08 66 89 50 04 90 c9 c3  |..E....E.f.P....|
00001920  55 89 e5 53 83 ec 14 8b  45 0c 88 45 f7 c6 45 f6  |U..S....E..E..E.|
00001930  f0 0f b6 45 f6 c1 e0 08  89 c2 0f b6 45 f7 09 d0  |...E........E...|
00001940  66 89 45 f4 0f b6 45 f7  83 f8 0a 75 11 8b 45 08  |f.E...E....u..E.|
00001950  0f b7 40 04 8d 50 50 8b  45 08 66 89 50 04 8b 45  |..@..PP.E.f.P..E|
00001960  08 8b 18 8b 45 08 0f b7  40 04 8d 48 01 8b 55 08  |....E...@..H..U.|
00001970  66 89 4a 04 0f b7 c0 01  c0 8d 14 03 0f b7 45 f4  |f.J...........E.|
00001980  66 89 02 90 8b 45 08 0f  b7 50 04 0f b7 c2 69 c0  |f....E...P....i.|
00001990  cd cc 00 00 c1 e8 10 66  c1 e8 06 66 89 45 f2 0f  |.......f...f.E..|
000019a0  b7 4d f2 89 c8 c1 e0 02  01 c8 c1 e0 04 29 c2 66  |.M...........).f|
000019b0  89 55 f2 8b 45 08 0f b7  40 04 0f b7 c0 69 c0 cd  |.U..E...@....i..|
000019c0  cc 00 00 c1 e8 10 66 c1  e8 06 66 89 45 f0 0f b7  |......f...f.E...|
000019d0  55 f0 89 d0 c1 e0 02 01  d0 c1 e0 04 89 c2 0f b7  |U...............|
000019e0  45 f2 01 d0 66 89 45 ee  83 ec 08 6a 0f 68 d4 03  |E...f.E....j.h..|
000019f0  00 00 e8 84 fe ff ff 83  c4 10 0f b7 45 ee 0f b6  |............E...|
00001a00  c0 83 ec 08 50 68 d4 03  00 00 e8 6c fe ff ff 83  |....Ph.....l....|
00001a10  c4 10 83 ec 08 6a 0e 68  d4 03 00 00 e8 5a fe ff  |.....j.h.....Z..|
00001a20  ff 83 c4 10 0f b7 45 ee  66 c1 e8 08 0f b6 c0 83  |......E.f.......|
00001a30  ec 08 50 68 d4 03 00 00  e8 3e fe ff ff 83 c4 10  |..Ph.....>......|
00001a40  90 8b 5d fc c9 c3 55 89  e5 83 ec 18 83 ec 0c 8d  |..]...U.........|
00001a50  45 f0 50 e8 50 fe ff ff  83 c4 10 83 ec 08 ff 75  |E.P.P..........u|
00001a60  08 8d 45 f0 50 e8 b6 fe  ff ff 83 c4 10 90 c9 c3  |..E.P...........|
00001a70  55 89 e5 83 ec 14 8b 45  08 66 89 45 ec 0f b7 45  |U......E.f.E...E|
00001a80  ec 89 c2 ec 88 45 ff 0f  b6 45 ff c9 c3 55 89 e5  |.....E...E...U..|
00001a90  83 ec 08 8b 45 08 8b 55  0c 66 89 45 fc 89 d0 88  |....E..U.f.E....|
00001aa0  45 f8 0f b6 45 f8 0f b7  55 fc ee 90 c9 c3 3a 00  |E...E...U.....:.|
00001ab0  00 00 03 00 27 00 00 00  01 01 fb 0e 0d 00 01 01  |....'...........|

这是实际加载到内存中的内容:

00000000  e9 00 00 00 00 55 89 e5  83 ec 08 83 ec 0c 6a 68  |.....U........jh|
00000010  e8 31 02 00 00 83 c4 10  83 ec 0c 6a 65 e8 24 02  |.1.........je.$.|
00000020  00 00 83 c4 10 83 ec 0c  6a 6c e8 17 02 00 00 83  |........jl......|
00000030  c4 10 83 ec 0c 6a 6c e8  0a 02 00 00 83 c4 10 83  |.....jl.........|
00000040  ec 0c 6a 6f e8 fd 01 00  00 83 c4 10 83 ec 0c 6a  |..jo...........j|
00000050  0a e8 f0 01 00 00 83 c4  10 90 c9 c3 55 89 e5 83  |............U...|
00000060  ec 18 8b 45 08 66 89 45  f4 0f b7 45 f4 83 ec 0c  |...E.f.E...E....|
00000070  50 e8 fa 01 00 00 83 c4  10 c9 c3 55 89 e5 83 ec  |P..........U....|
00000080  c0 83 ec 08 50 68 d4 03  00 00 e8 6c fe ff ff 83  |....Ph.....l....|
00000090  c4 10 83 ec 08 6a 0e 68  d4 03 00 00 e8 5a fe ff  |.....j.h.....Z..|
000000a0  ff 83 c4 10 0f b7 45 ee  66 c1 e8 08 0f b6 c0 83  |......E.f.......|
000000b0  ec 08 50 68 d4 03 00 00  e8 3e fe ff ff 83 c4 10  |..Ph.....>......|
000000c0  90 8b 5d fc c9 c3 55 89  e5 83 ec 18 83 ec 0c 8d  |..]...U.........|
000000d0  45 f0 50 e8 50 fe ff ff  83 c4 10 83 ec 08 ff 75  |E.P.P..........u|
000000e0  08 8d 45 f0 50 e8 b6 fe  ff ff 83 c4 10 90 c9 c3  |..E.P...........|
000000f0  55 89 e5 83 ec 14 8b 45  08 66 89 45 ec 0f b7 45  |U......E.f.E...E|
00000100  ec 89 c2 ec 88 45 ff 0f  b6 45 ff c9 c3 55 89 e5  |.....E...E...U..|
00000110  83 ec 08 8b 45 08 8b 55  0c 66 89 45 fc 89 d0 88  |....E..U.f.E....|
00000120  45 f8 0f b6 45 f8 0f b7  55 fc ee 90 c9 c3 3a 00  |E...E...U.....:.|
00000130  00 00 03 00 27 00 00 00  01 01 fb 0e 0d 00 01 01  |....'...........| 

似乎中间缺少一个连续的块。这可能是什么原因?

编辑:根据一些建议,我在in 中添加了缺少的volatile 并切换到48 位PIO,但问题仍然存在。

【问题讨论】:

看起来您有 24 位 lba 驱动程序。你需要的地方 48 @АлексейНеудачин 我虽然总是可以使用 24 位 lba,但我如何才能确定是否是这种情况?目前我只在使用 qemu,而不是在实际硬件上。 也许那时是可能的。然后你需要 90 年代后期的硬盘驱动器。我认为 quemu 应该提供一些模型 如果 quemu 将使用通用 ata 控制器,那么它将是 48 @АлексейНеудачин 这是有道理的,我认为 48 位驱动程序会“向后兼容”,但我想这没有多大意义。 【参考方案1】:

我想通了,我犯了两个非常微不足道的错误:首先ins 的代码不正确,正如 Michael Petch 指出的那样,它缺少 volatile。另外,dest 的类型应该是 T*

template<typename T>
void ins(uint16_t port, uint32_t dest, uint32_t count)

  static_assert(detail::is_uint_le32_t<T>());

  T *dest_ptr = reinterpret_cast<T *>(dest);

  asm volatile("rep ins%z2"
               : "+D" (dest_ptr), "+c" (count), "=m" (*dest_ptr)
               : "d" (port)
               : "memory");

那么,至关重要的是,pa_dest += DISK_SECT_SIZE / 4 行是错误的。这应该是 pa_dest += DISK_SECT_SIZE,因为 ins 读取整个扇区(128 个双字),而不仅仅是四分之一。

【讨论】:

将指针作为uint32_t dest 传递没有什么意义。使用uintptr_tvoid*,甚至T* @PeterCordes:在 OS 开发 afaik 中使用固定宽度整数和指针来表示地址的可互换性非常普遍。 在这里硬编码指针大小似乎毫无意义,只是让移植到 x86-64 变得更加困难。如果您使用了void*size_t,则无需针对64 位模式更改此代码。所以我的部分反对意见是,它是一个标准名称,如uint32_t,您不能将其重新定义为 64 位类型,甚至比整数类型更重要。 (虽然由于这个函数只是取消引用它,而不是将它拆分成 page : offset 部分或任何东西,可以说调用者应该是那个将它转换为 T* 的人,如果它还不是一个指针。)对于指针作为整数,使用 @ 987654337@ 或自定义类型 例如在ins(PORT, (uint32_t)&amp;struct.buf, size)这样的情况下,这对调用者来说只是纯粹的不便 @PeterCordes:x86_64 是个好点,我应该改一下。

以上是关于ATA 磁盘驱动程序“跳过”内存块的主要内容,如果未能解决你的问题,请参考以下文章

块设备驱动(使用内存模拟)

关于 ATA 的问题

如何跳过嵌套块中的异常

块设备驱动程序 - 了解收到的 ioctl

Linux下驱动开发_块设备驱动开发(内存模拟存储)

在 Databricks 中设置驱动程序内存配置