Page 1 of 1
ATA Status BSY bit never clears
Posted: Sun Feb 02, 2020 9:48 pm
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;
}
}
}
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 8:13 am
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...?
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 8:16 am
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.)
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 10:18 am
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?
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 10:34 am
by Korona
Is it even supported to send a request to one device, while switching to the other device, without waiting for completion?
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 11:22 am
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).
Re: ATA Status BSY bit never clears
Posted: Mon Feb 03, 2020 9:23 pm
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
, 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");
}
Re: ATA Status BSY bit never clears
Posted: Tue Feb 04, 2020 2:28 am
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.
Re: ATA Status BSY bit never clears
Posted: Tue Feb 07, 2023 12:55 pm
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.
Re: ATA Status BSY bit never clears
Posted: Wed Feb 08, 2023 11:14 am
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.
Re: ATA Status BSY bit never clears
Posted: Thu Feb 09, 2023 8:32 am
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.