ATA Status BSY bit never clears

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
wsavage
Posts: 4
Joined: Sun Jan 19, 2020 12:25 pm

ATA Status BSY bit never clears

Post by wsavage »

Hi all-
I've recently been trying to identify an ATA PIO drive, following the wiki's instructions. Reading the STATUS port works, but the BSY bit of the STATUS port never clears.

My code is in Rust, but it should hopefully be pretty self explanatory.

Code: Select all

pub fn init() {
    unsafe {
        io::outb(DRIVESEL, 0xE0);
        io::outb(SECTOR_COUNT, 0);
        io::outb(LBAL, 0);
        io::outb(LBAM, 0);
        io::outb(LBAH, 0);
        io::outb(COMMAND, ATACommand::IdentifyDevice as u8);

        if io::inb(STATUS) == 0 {
            println!("ATA: master not found");
        } else {
            println!("ATA: master found");
        }

        io::outb(DRIVESEL, 0xF0);
        io::outb(SECTOR_COUNT, 0);
        io::outb(LBAL, 0);
        io::outb(LBAM, 0);
        io::outb(LBAH, 0);
        io::outb(COMMAND, ATACommand::IdentifyDevice as u8);

        if io::inb(STATUS) == 0 {
            println!("ATA: slave not found");
        } else {
            println!("ATA: slave found");
        }

        while io::inb(STATUS).get_bit(BSY) {
            crate::hlt_loop();
        }

        if io::inb(STATUS).get_bit(DRQ) && !io::inb(STATUS).get_bit(ERR) {
            let mut data: [u16; 256] = [0; 256];
            for i in 0..data.len() {
                data[i] = io::inw(DATA);
                print!("{}", data[i]);
            }
        } else {
            println!("ATA: read error");
            return;
        }

    }
}
mallard
Member
Member
Posts: 280
Joined: Tue May 13, 2014 3:02 am
Location: Private, UK

Re: ATA Status BSY bit never clears

Post by mallard »

How are you determining that it never clears? Could it not be that "crate::hlt_loop();" never returns? If that function does what its name suggests (the HLT instruction) it definitely won't return unless/until there's a CPU interrupt, so are interrupts enabled and unmasked in the PIC?

Also, surely you should be status-polling after every command you send and before you trust the resulting status value...?
Image
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA Status BSY bit never clears

Post by Octocontrabass »

I'm pretty sure hlt_loop() is an infinite loop. It'll never return, even if the BSY bit eventually clears.

With that said, I wouldn't be surprised if it never clears. When no hardware is responding at a particular address, you'll read the open bus value. It's common (but not guaranteed) that open bus will have all bits set; in other words, it will look like the status register is always 0xFF.

Also, keep in mind that IDE is legacy hardware, and most new PCs don't support it at all. The wiki article you're reading was first written almost 12 years ago, and hasn't been updated to take this into account. You should search for IDE-compatible PCI devices before assuming an IDE controller exists and is present at a specific address. (Of course, if you're only using an emulator, you can cheat a little.)
wsavage
Posts: 4
Joined: Sun Jan 19, 2020 12:25 pm

Re: ATA Status BSY bit never clears

Post by wsavage »

Octocontrabass wrote: With that said, I wouldn't be surprised if it never clears. When no hardware is responding at a particular address, you'll read the open bus value. It's common (but not guaranteed) that open bus will have all bits set; in other words, it will look like the status register is always 0xFF.
It looks like you're right that STATUS is returning 0xFF. I don't know if this is related or not, but I'm running qemu with -machine q35, which seems to function the same as my hardware (Thinkpad X220). When I run regular qemu, I get a double fault when reading status.
Are there any more up to date resources for using ATA PIO, or should I just go with writing an AHCI driver?
Korona
Member
Member
Posts: 1000
Joined: Thu May 17, 2007 1:27 pm
Contact:

Re: ATA Status BSY bit never clears

Post by Korona »

Is it even supported to send a request to one device, while switching to the other device, without waiting for completion?
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA Status BSY bit never clears

Post by Octocontrabass »

wsavage wrote:I don't know if this is related or not, but I'm running qemu with -machine q35, which seems to function the same as my hardware (Thinkpad X220).
QEMU's q35 machine provides an AHCI controller instead of an IDE controller. Your code will only work with an IDE controller.
wsavage wrote:When I run regular qemu, I get a double fault when reading status.
Reading the status register can't directly cause a double fault. There must be a problem somewhere else. For example, if you've enabled interrupts without configuring the interrupt controllers, the timer will cause IRQ0 which is by default mapped to interrupt 8 which is indistinguishable from a double fault.
wsavage wrote:Are there any more up to date resources for using ATA PIO, or should I just go with writing an AHCI driver?
This page explains how to search the PCI bus, and this page describes what to look for. In short, you're looking for a device with class 0x01 and subclass 0x01, and if you find it you'll read BAR0 and BAR1 to determine the correct I/O ports.

With QEMU, you can cheat a bit: the default "pc" (i440fx) machine always has an IDE controller mapped at the legacy addresses, so you can skip all of the PCI stuff and keep doing what you're doing now. Then, once you have your driver working, you can add PCI support to make it work with a wider variety of hardware (and make sure it doesn't try to run when there is no IDE controller).
wsavage
Posts: 4
Joined: Sun Jan 19, 2020 12:25 pm

Re: ATA Status BSY bit never clears

Post by wsavage »

Thanks for the help. The cause of the double fault was a lack of interrupt handlers, as you had said. When I run without

Code: Select all

machine -q35
, status reads 88 for both master and slave. The wiki, as well as other code examples that I have looked at, say to read 256 u16s from the data port to get device information. However, when I do this, only 2/3s of the values are actual numbers, and the rest are zeroes. A screenshot of the output is attached. Am I doing something wrong here? I can't seem to find much about this online.

Here's my read code. Apologies for rust.

Code: Select all

      let mut raw: [u16; 256] = [0; 256];
        if io::inb(STATUS).get_bit(DRQ) && !io::inb(STATUS).get_bit(ERR) {
            for i in 0..raw.len() {
                raw[i] = io::inw(DATA);
                print!("{}", raw[i]);
            }
        } else {
            println!("ATA: read error");
        }
Attachments
wrong3.png
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA Status BSY bit never clears

Post by Octocontrabass »

wsavage wrote:Am I doing something wrong here?
It looks right to me, although the way you're displaying those numbers makes it very difficult to interpret the results.

Look for the IDENTIFY DEVICE section in the ATA specification if you want to know how to interpret that data. A lot of it is considered obsolete, so you might have to look at several different versions of the ATA specification to understand all of the information the drive gives you.
lapfed255
Posts: 2
Joined: Tue Mar 29, 2022 10:49 am

Re: ATA Status BSY bit never clears

Post by lapfed255 »

Sorry for reviving this topic, however got the same problem.

I tested a lot of things, but none of them seem to work.
My ATA identify structure (taken from MSDN) looks like this:

Code: Select all

struct ata_identify {
  struct {
    uint16_t reserved1 : 1;
    uint16_t obsolete1 : 1;
    uint16_t response_incomplete : 1;
    uint16_t obsolete2 : 3;
    uint16_t fixed_device : 1;
    uint16_t removable_media : 1;
    uint16_t obsolete3 : 7;
    uint16_t atapi : 1;
  } general_info;
  uint16_t num_cylinders;
  uint16_t specific_configuration;
  uint16_t num_heads;
  uint16_t retired1[2];
  uint16_t num_sectors_per_track;
  uint16_t vendor_unique1[3];
  char serialnumber[20];
  uint16_t deprecated[2];
  uint16_t obsolete1;
  char firmware_revision[8];
  char model_number[40];
  char maximum_block_transfer;
  char vendor_unique2;

} __pack;

I have looked into specification of ATA, it should be correct.

The polling and data code:

Code: Select all

  bool drq = false, err = false;
  status = ata_read_status(dev);
  klogf("polling ata status: %d", status);
  while (!(err = (status & ATA_STATUS_ERR) != 0) &&
         !(drq = (status & ATA_STATUS_DRQ) != 0)) {
    klogf("polling ata status: %d", status);
    if (inb(dev->pio_base + ATA_REG_LBA_MID) ||
        inb(dev->pio_base + ATA_REG_LBA_HI)) {
      klogf("device is not ata");
      kfree(data, 256 * sizeof(uint16_t));
      return NULL;
    }
    status = ata_read_status(dev);
  }

  klogf("finished polling ata status: %d (err=%d drq=%d)", status, err, drq);

  if (err) {
    klogf("error reading ata device: err=%d drq=%d", err, drq);
    return NULL;
  }

  for (int i = 0; i < 256; i++)
    *(uint16_t *)(data + i) = inw(dev->pio_base + ATA_REG_DATA);
Te data from this code is not meaningful. The model & serial number are incorrect and consist of junk characters. Number of cylinders and heads also seems way too big (screenshot).

The emulator I'm using is QEMU, and I've attached the drives using the following snippet

Code: Select all

QEMU=qemu-system-i386
QEMU_ARGS="-M pc-i440fx-2.8 -m 4G -monitor none -serial stdio"
BOOT_ISO=myiso.iso

$QEMU $QEMU_ARGS \
  -drive id=boot,file=$BOOT_ISO,format=raw,if=none,unit=0 \
  -drive id=disk1,file=mydisk.img,format=raw,if=none,unit=1 \
  -device ide-hd,drive=boot,bus=ide.0,serial=hello,model=world -device ide-hd,drive=disk1,bus=ide.0

Thanks in advance.
Attachments
The model is model_number and serial is serialnumber
The model is model_number and serial is serialnumber
WindowsTerminal_qF4mx8Yoa2.png (7.99 KiB) Viewed 2267 times
Octocontrabass
Member
Member
Posts: 5562
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA Status BSY bit never clears

Post by Octocontrabass »

lapfed255 wrote:

Code: Select all

    *(uint16_t *)(data + i) = inw(dev->pio_base + ATA_REG_DATA);
Unless "data" is already a pointer to a type compatible with uint16_t, this code is not going to fill the buffer correctly and might even be undefined behavior.
lapfed255
Posts: 2
Joined: Tue Mar 29, 2022 10:49 am

Re: ATA Status BSY bit never clears

Post by lapfed255 »

Octocontrabass wrote:
lapfed255 wrote:

Code: Select all

    *(uint16_t *)(data + i) = inw(dev->pio_base + ATA_REG_DATA);
Unless "data" is already a pointer to a type compatible with uint16_t, this code is not going to fill the buffer correctly and might even be undefined behavior.
Yep that was the problem. I've already solved it, but thanks nonetheless.
Post Reply