I’m encountering an issue in which my PS/2 mouse driver will move erratically or lock to the top edge of the screen, indicating my PS/2 mouse state machine has fallen out-of-sync with the physical mouse. This happens exclusively if I perform too much computation in the mouse ISR.
The issue initially popped up when I had too many printf()’s along the code path that the mouse ISR executes, but in the name of confidence and reproducibility, I’ve added a fibonacci(20) to my spinlock implementation. The problem reproduces and disappears consistently following whether I include the fibonnacci(20) (or printf’s).
I’ve tested a few different hypotheses that I’ve detailed below, but first I’ll provide some background on the system.
Handling mouse events
The handling of mouse events is split into 3 parts:
1. An ISR handles the mouse interrupt, reads a byte from the PS/2 port, and sends an IPC message to the PS/2 mouse driver
2. The PS/2 mouse driver receives an IPC message containing a byte of data and updates the state machine building a full (3-byte) data packet.
3. Once a data packet has been built, the PS/2 mouse driver sends an IPC message to the window manager, containing the 3 data bytes
Inter-process communication
Inter-process-communication works like so:
1. A process registers itself to the IPC subsystem
Sending a message
1. The data structure for the message is dynamically allocated, then populated
2. The data structure for the message is appended to the receiver’s inbox
3. If the receiver was blocked due to awaiting a message, the receiver is unblocked
4. If the receiver is of a higher priority than the current task, the current task is preempted.
Receiving a message
1. A process states its intent to receive a message via a syscall
2. The process is blocked until a message is received (if its inbox is empty)
3. A message is popped from its inbox (in FIFO) and the message is provided as the syscall’s return value
Modification and iteration of a process’s IPC inbox is protected by a spinlock, as is the kernel heap.
There are exactly two priorities at the moment: the priority for the second-stage PS/2 mouse driver (high), and everything else (low).
Okay! Thanks for bearing with me up to now. I’ve had some ideas as to what might be going on. Here’s what the mouse ISR looks like, for more context. I’ve flattened it into a single function to show control flow:
Code: Select all
void interrupt_handler(registers_t* regs) {
// Save state
// …
if (regs->int_no == PS2_MOUSE_IRQ) {
uint8_t byte = ps2_read(PS2_DATA);
// The “__from_core” variant ensures that messages originating from ISR’s are sent from the special
// “com.axle.core” sender, instead of whatever service is running outside of the ISR context
amc_message_t* amc_msg = amc_message_construct__from_core(&byte, 1);
// Forward the data byte to the “stage 2” mouse driver
amc_message_send("com.axle.mouse_driver", amc_msg);
}
pic_signal_end_of_interrupt(regs->int_no);
// Restore state
// …
// sti
// iretd
}
As you can see, `amc_message_send` is called near the end of the mouse ISR. This function contains code like the following:
Code: Select all
bool amc_message_send(const char* destination_service, amc_message_t* msg) {
// …
// Unblock the receiver if it was waiting for a message
if (dest->task->blocked_info.status == AMC_AWAIT_MESSAGE) {
tasking_unblock_task(dest->task, false);
// Higher priority tasks that were waiting on a message should preempt a lower priority active task
if (dest->task->priority > get_current_task_priority()) {
tasking_goto_task(dest->task);
}
}
// …
}
Code: Select all
bool amc_message_send__from_isr(const char* destination_service, amc_message_t* msg) {
return _amc_message_send_int(destination_service, msg, false);
}
bool _amc_message_send_int(const char* destination_service, amc_message_t* msg, bool allow_preemption_for_high_priority_unblock) {
// …
// Higher priority tasks that were waiting on a message should preempt a lower priority active task
if (dest->task->priority > get_current_task_priority()) {
// But don't jump away if we're coming from an interrupt handler
if (allow_preemption_for_high_priority_unblock) {
tasking_goto_task(dest->task);
}
}
// …
}
The behavior I’m seeing happens specifically when the mouse ISR spends too much time computing. Could it be that the mouse ISR is conflicting with the PIT ISR?
To test this, I added a “disable scheduler” flag. The mouse ISR disables scheduling when it begins, and re-enables scheduling when it ends. The mouse ISR now looks like the following:
Code: Select all
// …
if (regs->int_no == PS2_MOUSE_IRQ) {
tasking_disable_scheduling();
uint8_t byte = ps2_read(PS2_DATA);
amc_message_t* amc_msg = amc_message_construct__from_core(&byte, 1);
amc_message_send__from_isr(“com.axle.mouse_driver", amc_msg);
tasking_reenable_scheduling();
}
// …
Does anyone have a clue what the root cause could be here? Thanks very much!