GNUX solves this problem by splitting ISR into two parts:
TH saves data to a device specific buffer.
TH finished quickly so it can service new interrupts while BH processing data.
TH must finish quick because new data arriving while TH running would be lost.
Every serious (e.g. if it's not a joke) interrupt handler split into TH and BH.
example of network interface: TH gets packet data; BH processes the data.
task queues evolved from the notion of a BH. (TH and BH existed long before task queues).
See irq.c (a very simple teaching example for lab7).