I generally do as little work as possible in the ISR to secure the received data or clean up the transmitted data. This will usually mean reading data out of the hardware buffers and into a circular buffer.
On receive, for a multi-threaded os, a receive interrupt empties the hardware, clears the interrupt and signals a thread to service the received data.
For a polling environment, a receive interrupt empties the harwdware, clears the interrupt, and sets a flag to notify the polling loop that it has something to process.
Since interrupts can occur any time the data structures shared between the ISR and the polling loop or processing thread must be protected using a mutual exclusion mechanism.
Often this will mean disabling interrupts briefly while you adjust a pointer or count.
If the received data is packetized you can hunt for packet boundaries in the ISR
and notify the handler only when a full packet has arrived.