Page 2 of 2

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 3:06 am
by linuxyne
onlyonemac wrote: It's the recalibrate command that's hanging at the moment;
What is being done in the 4th step? (I am assuming that the recaliberate was the cmd for which the 1st interrupt arrived).
1. Configure fdc for recalibration
2. wait() for interrupt #1
3. Receive interrupt #1
4. ???
5. wait() for interrupt #2
onlyonemac wrote: Currently I'm not doing anything to avoid receiving nested interrupts. I can receive a second interrupt while the first one is still being processed. Should I try to avoid this (e.g. cli on entry to the asm wrapper and sti before iret)?
I assumed that nesting (of hw ints) was enabled. The cpu itself (through appropriate IDT entries) must be disabling the interrupts before calling your asm wrapper (you can check the eflags in the irq routines). If the routines and the wrapper do not explicitly enable the interrupts themselves (by using sti, for e.g.), the nesting (on the same cpu) is avoided. Else, as SpyderTL suggested, counters/resets/synchronisation is necessary to properly manage nested/parallel interrupts.

In general, synchronising multiple related threads of varying importance, which run truly in parallel, or one of which can abruptly preempt the other, requires non-trivial handling.
It needs forcing the compiler and the cpu to provide guarantees around memory accesses and insn re-ordering.
It needs forcing the OS to provide guarantees around preemption.

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 9:00 am
by onlyonemac
linuxyne wrote:What is being done in the 4th step? (I am assuming that the recaliberate was the cmd for which the 1st interrupt arrived).
1. Configure fdc for recalibration
2. wait() for interrupt #1
3. Receive interrupt #1
4. ???
5. wait() for interrupt #2
No, no. The first interrupt is after a reset, the second interrupt (that's not arriving) is after the recalibrate. Here's the code:

Code: Select all

<somewhere in FDC initialisation function>
fdc_reset();
fdc_motor_on();
fdc_do_command(FDC_CMD_RECALIBRATE, (uint8_t[]){ 0 }, NULL);
fdc_do_command(FDC_CMD_SENSE, NULL, fdc_results);
fdc_motor_off();

Code: Select all

static void fdc_reset()
{
	sys_log("FDC: reset");

	instance->irq = FALSE;
	outb(FDC_REGISTER_DSR, 0x80);
	fdc_wait_for_irq();
	fdc_do_command(FDC_CMD_SPECIFY, (uint8_t[]){ (8 << 4) | 5, 15 << 1 }, NULL);
}

Code: Select all

static void fdc_motor_on()
{
	outb(FDC_REGISTER_DOR, 0x18);
}

Code: Select all

static void fdc_motor_off()
{
	outb(FDC_REGISTER_DOR, 0x08);
}

Code: Select all

static void fdc_do_command(fdc_command command, uint8_t* parameters, uint8_t* results)
{
	uint32_t	current_parameter;
	uint32_t	parameter_count;
	uint32_t	current_result;
	uint32_t	result_count;
	uint32_t	irq_expected;

	if ((inb(FDC_REGISTER_MSR) & 0xC0) != 0x80)
	{
		fdc_reset();
		fdc_do_command(command, parameters, results);
		return;
	}

	switch (command)
	{
		case FDC_CMD_READ:
		case FDC_CMD_WRITE:
			parameter_count = 8;
			result_count = 7;
			irq_expected = TRUE;
			break;
		case FDC_CMD_SEEK:
			parameter_count = 2;
			result_count = 0;
			irq_expected = TRUE;
			break;
		case FDC_CMD_RECALIBRATE:
			parameter_count = 1;
			result_count = 0;
			irq_expected = TRUE;
			break;
		case FDC_CMD_SENSE:
			parameter_count = 0;
			result_count = 2;
			irq_expected = FALSE;
			break;
		case FDC_CMD_CONFIGURE:
			parameter_count = 3;
			result_count = 0;
			irq_expected = FALSE;
			break;
		case FDC_CMD_SPECIFY:
			parameter_count = 2;
			result_count = 0;
			irq_expected = FALSE;
			break;
		case FDC_CMD_LOCK:
		case FDC_CMD_UNLOCK:
			parameter_count = 0;
			result_count = 1;
			irq_expected = FALSE;
			break;
		case FDC_CMD_VERSION:
			parameter_count = 0;
			result_count = 1;
			irq_expected = FALSE;
			break;
	}

	if (irq_expected == TRUE)
	{
		instance->irq = FALSE;
	}
	outb(FDC_REGISTER_FIFO, command);
	current_parameter = 0;
	while (current_parameter < parameter_count)
	{
		while ((inb(FDC_REGISTER_MSR) & 0xC0) != 0x80)
		{
			// wait
		}
		outb(FDC_REGISTER_FIFO, parameters[current_parameter]);
		current_parameter++;
	}
	if (irq_expected == TRUE)
	{
		fdc_wait_for_irq();
	}
	current_result = 0;
	while (current_result < result_count)
	{
		while ((inb(FDC_REGISTER_MSR) & 0xC0) != 0xC0)
		{
			// wait
		}
		results[current_result] = inb(FDC_REGISTER_FIFO);
		current_result++;
	}
}
SpyderTL wrote:
Currently I'm not doing anything to avoid receiving nested interrupts. I can receive a second interrupt while the first one is still being processed. Should I try to avoid this (e.g. cli on entry to the asm wrapper and sti before iret)?
Either handle your interrupt entirely inside your interrupt handler (which prevents nested interrupts), or make your interrupt flag a counter, and separately keep track of which interrupt you are currently handling.

Specifically, for your code, you would have to set the IRQ flag back to FALSE inside your interrupt handler, before your IRET statement.
Sorry, I'm not understanding. What's happening is that drivers register an interrupt handler with the kernel for a specific irq, then when the kernel receives an interrupt it dispatches it (through a combination of an assembly wrapper and a dispatcher function as previously posted) to the interrupt handler registered for the irq received (if present). Once the driver's interrupt handler returns, the kernel sends the EOI to the PIC (if it's a hardware interrupt) and then does the iret (back in the assembly wrapper). (If there's no handler registered for an interrupt, the kernel sends EOI and returns straight away.)

As it is, it is possible for a second interrupt to come while the first interrupt handler is still executing. I thought this was normal behaviour though, for a higher-priority interrupt to interrupt a lower-priority one. However, there is no way for the interrupt handlers to become "confused" as to which interrupt is being handled, as each handler is registered for a specific irq only and also receives as a parameter the irq of the interrupt that it is handling (as technically a handler might be registered for more than one irq). The only way that there could be an issue here is if a second FDC interrupt arrived before the handler for the first one had returned, although that isn't going to happen because the rest of the FDC driver waits for the interrupt before sending the next command.

The assembly wrapper is as follows:

Code: Select all

#define create_isr_wrapper(irq)\
uint32_t irq_##irq##_handler()\
{\
	uint32_t	current_offset;\
	asm goto("jmp %l[isr_end]":::"memory":isr_end);\
\
	isr_start:\
	asm volatile("pushal\npushl %1\ncall %0\nadd $4, %%esp\npopal\niret"::"r"((uint32_t) 0x01234567), "r"((uint32_t) irq):"memory");\
	isr_end:\
\
	current_offset = 0;\
	while (*((uint32_t*) (&&isr_start + current_offset)) != 0x01234567)\
	{\
		current_offset++;\
	}\
	*((uint32_t*) (&&isr_start + current_offset)) = irq_handler;\
	return (uint32_t) &&isr_start;\
}
Then I do near the top of the source file:

Code: Select all

create_isr_wrapper(0x20);
create_isr_wrapper(0x21);
create_isr_wrapper(0x22);
create_isr_wrapper(0x23);
<etc.>
And in my initialisation routine:

Code: Select all

add_idt_entry(0x20, irq_0x20_handler());
add_idt_entry(0x21, irq_0x21_handler());
add_idt_entry(0x22, irq_0x22_handler());
add_idt_entry(0x23, irq_0x23_handler());
<etc.>
As far as I can tell, this code is working fine. If I unmask the timer interrupt I get repeated timer interrupts, and if I press a key I get one interrupt from the PS/2 controller (although no more as I believe you have to retrieve the key from the PS/2 controller before it will send another key).

Although I've forgotten about some peculiarity in the x86 architecture where one or more irqs are automatically masked/disabled when an interrupt occurs, there's nothing to stop a second interrupt occurring while the first one is being processed. For that matter, I posted a somewhat interesting and detailed thread about this a while ago: http://forum.osdev.org/viewtopic.php?f=1&t=30034.

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 12:26 pm
by sleephacker
I don't know if this is the exact same issue or not, but I've been unable to get an IRQ 6 after a recalibrate command on bochs, but it has worked on virtualbox and real hardware. On bochs the recalibrate command worked properly despite not generating an interrupt.
So if you've only tested it on bochs so far it might be a good idea to test on a different emulator to see if that works.
I've also noticed that different emulators as well as real hardware all seem to disagree on when they send interrupts and how many.

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 12:51 pm
by onlyonemac
sleephacker wrote:I don't know if this is the exact same issue or not, but I've been unable to get an IRQ 6 after a recalibrate command on bochs, but it has worked on virtualbox and real hardware. On bochs the recalibrate command worked properly despite not generating an interrupt.
This is QEMU. How's QEMU with FDC interrupts?

EDIT: Skipping the IRQ 6 after recalibrate seems to work, while I still get (second and subsequent) IRQ 6 after seek. Would polling ACTA/ACTB/ACTC/ACTD in the MSR be a suitable way to determine when the recalibrate is complete?

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 1:16 pm
by onlyonemac
Now I'm testing it on a real machine and I'm not getting the interrupt after the reset. In other words,

Code: Select all

instance->irq = FALSE;
outb(FDC_REGISTER_DSR, 0x80);
fdc_wait_for_irq();
fdc_do_command(FDC_CMD_SPECIFY, (uint8_t[]){ (8 << 4) | 5, 15 << 1 }, NULL);
which has been working all along in QEMU isn't working on my real machine.

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 1:49 pm
by sleephacker
onlyonemac wrote:This is QEMU. How's QEMU with FDC interrupts?
I've never tested on QEMU...
onlyonemac wrote:Skipping the IRQ 6 after recalibrate seems to work, while I still get (second and subsequent) IRQ 6 after seek. Would polling ACTA/ACTB/ACTC/ACTD in the MSR be a suitable way to determine when the recalibrate is complete?
Yes.
I used the port 0xE9 hack to detect if it's running on bochs and if so I just skip waiting for the command to be complete entirely. Maybe there is a similar trick for QEMU...

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 2:10 pm
by onlyonemac
onlyonemac wrote:Now I'm testing it on a real machine and I'm not getting the interrupt after the reset. In other words,

Code: Select all

instance->irq = FALSE;
outb(FDC_REGISTER_DSR, 0x80);
fdc_wait_for_irq();
fdc_do_command(FDC_CMD_SPECIFY, (uint8_t[]){ (8 << 4) | 5, 15 << 1 }, NULL);
which has been working all along in QEMU isn't working on my real machine.
Whoops, I was setting the RESET bit in the DOR the wrong way round. It's supposed to be set to clear reset mode; clear to enable reset mode.

Now I need to figure out why every command's failing.

Re: Problem receiving IRQ in driver

Posted: Sat Aug 06, 2016 2:43 pm
by onlyonemac
Got the commands working. Seems to be a race condition that I can't (yet) figure out how to avoid. On the emulators, the FDC is ready for the next command before I try to send it; on real hardware the FDC is too slow and the commands keep failing at step 2 and sending the driver into an endless loop of resetting the FDC. So now I'm just looping until RQM == 1 and DIO == 0 (without resetting the FDC), but there's no way for me to detect an FDC lockup vs FDC-being-too-slow. I guess I should have a timeout, but first I need to get the PIT set up in a usable way.

Also my recalibrate commands don't seem to be moving the heads back to track 0 on real hardware. I'll have to take the cover off my floppy drive and see what's going on in there.

Re: Problem receiving IRQ in driver

Posted: Mon Aug 08, 2016 12:02 pm
by onlyonemac
Turns out that my real machine does *not* let me poll ACTA in the MSR to determine when the recalibrate is complete (the recalibrate command completes "instantly" and if I send another command it just aborts the recalibrate and goes on with the next command) and does in fact send an IRQ 6 when done, while QEMU doesn't send an IRQ 6. Is there anything else that I can poll on the real machine?

Re: Problem receiving IRQ in driver

Posted: Tue Aug 09, 2016 4:55 am
by sleephacker
onlyonemac wrote:Turns out that my real machine does *not* let me poll ACTA in the MSR to determine when the recalibrate is complete (the recalibrate command completes "instantly" and if I send another command it just aborts the recalibrate and goes on with the next command) and does in fact send an IRQ 6 when done, while QEMU doesn't send an IRQ 6. Is there anything else that I can poll on the real machine?
There is a command busy bit in the MSR, but it is useless for commands that don't have a result phase (such as recalibrate).
The only thing I can come up with is to wait for the IRQ 6 until a timer runs out, and if by then you still haven't received an IRQ 6 you can send a READ ID command which will (after an IRQ 6 and a sense interrupt status command) give you the current head/cylinder position, or time out/fail if the FDC broke. Using the current track you could then determine if the recalibrate succeeded or if it should be retried. This would work both on QEMU and real hardware.
The READ ID command takes one parameter byte: (head << 2 | drive).