zity wrote:
However, at least in QEMU, the IRQ handler fires almost immediately, which means that sometimes the ATA thread never manages to actually execute the thread_block() function before the IRQ handler has tried to unblocked the thread. As a consequence, the thread_block() function is called after the thread_unblock(), which means that the ATA thread blocks forever.
I must be approaching this in the wrong way, because the problem is quite generic. How do I handle situations like this properly?
My first thought was: "This looks like a job for a semaphore." But more generally, if you want to call thread_unblock() from an IRQ handler, then you have to CLI from the last time you check that the IRQ has not arrived until at least thread_block() has made changes that thread_unblock() will pick up. This way the IRQ handler can't run at an inopportune time. I'd suggest handing thread_block() an address and an expected value (or writing another function that does that... maybe thread_block_if_equal()?) That version of thread_block() will then CLI, take a spinlock (maybe a global one), ensure the value at the address is still the expected value, and if so, add the current thread to the list of wakable threads. Then it can release the lock and STI again before blocking until the thread is woken.
The IRQ handler must then take the same spinlock before changing the value (I'm thinking your request structure has a "done" field, which the IRQ handler sets to 1). Then it can unblock the thread and release the spinlock.
The spinlock is needed for SMP safety. If you do not support SMP, nor intend to in the future, you do not need it. But I always think it is better to design with parallelism in mind, rather than patching it in later.
By the way: If you export the above mechanism into userspace, you basically have a futex mechanism, which is a really powerful tool to let the userspace handle its synchronization needs itself.