Vector of subroutine pointers in NASM

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
frabert
Member
Member
Posts: 38
Joined: Wed Jul 25, 2018 2:47 pm
Location: Pizzaland, Southern Europe
Contact:

Vector of subroutine pointers in NASM

Post by frabert »

I'm currently following JamesM's tutorials, and I'd like to "clean up" the IRQ/ISR code a bit by reducing the amount of repetition it has. Right now, it does some NASM macro trickery to generate the different stubs for the various ISRs, which is already great for reducing the amount of assembly code required, but does little to reduce the C side of things, which looks like this for me:

Code: Select all

  idt_entries [0] = IdtEntry::makeEntry(isr0,  0x08, PrivilegeLevel::Ring0);
  idt_entries [1] = IdtEntry::makeEntry(isr1,  0x08, PrivilegeLevel::Ring0);
  idt_entries [2] = IdtEntry::makeEntry(isr2,  0x08, PrivilegeLevel::Ring0);
  // ...
  idt_entries[31] = IdtEntry::makeEntry(isr31, 0x08, PrivilegeLevel::Ring0);

  idt_entries[32] = IdtEntry::makeEntry(irq0,  0x08, PrivilegeLevel::Ring0);
  idt_entries[33] = IdtEntry::makeEntry(irq1,  0x08, PrivilegeLevel::Ring0);
  // ...
  idt_entries[47] = IdtEntry::makeEntry(irq15, 0x08, PrivilegeLevel::Ring0);
Ideally, I'd like to turn that into something like this:

Code: Select all

for(int i = 0; i < num_isrs; i++) {
  idt_entries[i] = IdtEntry::makeEntry(irqs[i], 0x08, PrivilegeLevel::Ring0);
}
To do that, I though of reserving some space in the assembly code like this:

Code: Select all

isr_handlers: resd 32
irq_handlers: resd 16
So that I can put the pointers to all the different isrs into those two vectors... But I have no idea how to do it, or even if it's possible! Does NASM support this? Can I populate a reserved section with pointers?
frabert
Member
Member
Posts: 38
Joined: Wed Jul 25, 2018 2:47 pm
Location: Pizzaland, Southern Europe
Contact:

Re: Vector of subroutine pointers in NASM

Post by frabert »

Sorry, I should have made a better research the first time, I figured it out.
This is the final code I've come up with:

Code: Select all

%macro ISR_NOERRCODE 1  ; define a macro, taking one parameter
  [GLOBAL isr%1]        ; %1 accesses the first parameter.
  isr%1:
    cli
    push byte 0
    push byte %1
    jmp isr_common_stub
%endmacro

%macro ISR_ERRCODE 1
  [GLOBAL isr%1]
  isr%1:
    cli
    push byte %1
    jmp isr_common_stub
%endmacro

; This macro creates a stub for an IRQ - the first parameter is
; the IRQ number, the second is the ISR number it is remapped to.
%macro IRQ 2
  [GLOBAL irq%1]
  irq%1:
    cli
    push byte 0
    push byte %2
    jmp irq_common_stub
%endmacro

%macro ISR_ADDR 1
  dd isr%1
%endmacro

%macro IRQ_ADDR 1
  dd irq%1
%endmacro

ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
; ...
IRQ   0,      32
IRQ   1,      33
IRQ   2,      34
; ...

[EXTERN isr_handler]

; This is our common ISR stub. It saves the processor state, sets
; up for kernel mode segments, calls the C-level fault handler,
; and finally restores the stack frame.
isr_common_stub:
  ;... isr stuff

[EXTERN irq_handler]

; This is our common IRQ stub. It saves the processor state, sets
; up for kernel mode segments, calls the C-level fault handler,
; and finally restores the stack frame.
irq_common_stub:
  ;... irq stuff

[GLOBAL isr_handlers]
isr_handlers:
  %assign i 0 
  %rep    32
    ISR_ADDR i
  %assign i i+1 
  %endrep
  
  %assign i 0 
  %rep    16
    IRQ_ADDR i
  %assign i i+1 
  %endrep
It's mostly JamesM's code, but I though someone else following his tutorial might find it interesting nonetheless. I use it from the C++ code like this:

Code: Select all

extern "C" IdtEntry::interrupt_handler *isr_handlers[];
...

for(int i = 0; i < 48; i++) {
  idt_entries[i] = IdtEntry::makeEntry(isr_handlers[i], 0x08, PrivilegeLevel::Ring0);
}
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: Vector of subroutine pointers in NASM

Post by Schol-R-LEA »

Before I answer (or at least ask for further clarification), let me make an aside (less for you personally than for other newcomers who are seeing this). The James Molloy tutorial - as with all of the existing OS dev tutorials - is seriously flawed, with known bugs and poor choices, as well as being somewhat out of date. It has far fewer problems than some other ones (the contemptibly awful Darnell book comes to mind), but they do exist.

I am only mentioning this now because this disclaimer is a frequent piece of due-diligence for the regulars here. It isn't directly relevant to the question, as it happens.

For the OP, this is probably less of an issue, as the main problem is when someone follows the instructions blindly, rather than viewing them critically and getting additional sources of information. Frabert has already shown just that sort of initiative in their short time here, but that make them the exception, not the rule.

EDIT: I was about to try answering the question, but I wanted to make sure that this point was made immediately. After posting this, I saw that the OP had found their answer while I was composing this, so I think I can leave this as it is. Also, I am not sure if the JamesM listed as part of the Pedigree OS project is Molloy or not, buty either way, while it is possible that the OP is talking about a tutorial from a different JamesM, I doubt this is the case since Molloy's is the only one I know of from anyone using that name.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
frabert
Member
Member
Posts: 38
Joined: Wed Jul 25, 2018 2:47 pm
Location: Pizzaland, Southern Europe
Contact:

Re: Vector of subroutine pointers in NASM

Post by frabert »

Oooh thanks for the link, nice to know where the pitfalls are! Fortunately I managed to avoid or solve many of those, but it will come in handy nonetheless :)
EDIT: I also remember seeing Pedigree OS listed on http://www.jamesmolloy.co.uk back when it was still working properly, that's why I thought it was the same JamesM.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Vector of subroutine pointers in NASM

Post by Brendan »

Hi,
frabert wrote:Ideally, I'd like to turn that into something like this:

Code: Select all

for(int i = 0; i < num_isrs; i++) {
  idt_entries[i] = IdtEntry::makeEntry(irqs[i], 0x08, PrivilegeLevel::Ring0);
}
Don't forget that:
  • For the first 32 interrupts (for exceptions) some don't exist (0x0F, 0x15, 0x16, ..., 0x1F) and their IDT entries should be marked "not present"; and some may or may not exist depending on which features the CPU supports (e.g. "0x14, Virtualisation Exception" doesn't exist if the CPU doesn't support hardware virtualisation).
  • For the first 32 interrupts, for some of them (double fault, NMI, machine check) you may need to use task gates (for protected mode) or IST (for long mode), so they end up being different to other exceptions (for more than just the "address of interrupt handler" part)
  • If the OS uses PIC chips (instead of IO APICs), IRQ2 doesn't exist (is used for a "cascade" line)
  • If the FPU is set to "native exceptions" (strongly recommended) and the OS uses PIC chips, then IRQ15 ("FPU error IRQ") doesn't exist
  • If the FPU is not set to "native exceptions" then exception "0x10, floating point exception" doesn't exist
  • If the OS uses IO APICs, there can be any number of IO APICs where each one can have any number of inputs/IRQs
  • If the OS uses IO APICs and PIC chips don't exist, you shouldn't create any IDT entries for any PIC chip IRQs
  • If the OS uses IO APICs and PIC chips do exist, you should create IDT entries for the PIC chips' spurious IRQs (but no other PIC chip IRQs)
  • If the OS uses IO APICs and supports MSI or MSI-X, you'd need to be able to dynamically allocate groups of multiple consecutive vectors
  • If the OS supports SMP; then it will need some vectors for "inter-processor interrupts" (IPIs)
  • If the OS supports "large SMP" systems; then you'll want something to balance IRQs (e.g. so a single CPU doesn't get swamped by IRQs while other CPUs are doing nothing)
  • If the OS supports "very large SMP" systems; then each CPU or group of related CPUs can have a different IDT to allow you to avoid having a "max. of no more than 224 IRQs for the entire computer" limitation
  • If the OS uses IO APICs, or if the OS supports SMP; then the OS will need to use the local APIC and therefore must assign a vector for the local APIC's spurious IRQ (but may also want to assign more vectors for things like the local APIC's thermal monitor interrupt, performance monitoring overflow, timer, ...)
  • For all interrupts from IO APIC or local APIC; the interrupt vector determines the priority of the interrupt, so you want important things (e.g. "multi-CPU TLB shootdown IPI") using higher priority vectors and less important things (floppy drive controller IRQ) using lower priority vectors; and don't want the vector number to depend on how the where the interrupt is connected (e.g. like "vector = IO APIC input number + 32").
  • Most of the things mentioned above depend on some kind of auto-detection (e.g. CPUID, ACPI's MADT, PCI bus enumeration)
Mostly; a single static table is a temporary step that will become far too inflexible very quickly.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
frabert
Member
Member
Posts: 38
Joined: Wed Jul 25, 2018 2:47 pm
Location: Pizzaland, Southern Europe
Contact:

Re: Vector of subroutine pointers in NASM

Post by frabert »

Wow, that's a lot more complicated that I had initially hoped... Do you have any links for where I can look this stuff more in detail? I checked on the wiki but it seems there's just mostly generic info
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Vector of subroutine pointers in NASM

Post by Brendan »

Hi,
frabert wrote:Wow, that's a lot more complicated that I had initially hoped... Do you have any links for where I can look this stuff more in detail? I checked on the wiki but it seems there's just mostly generic info
Information for exceptions and local APICs is in Intel's Programmer's Reference Manual. For the PIC chips, there's a link to an appropriate datasheet at the bottom of the wiki page. IO APICs are covered by various datasheets (there's a link at bottom of wiki page for one), Intel's MultiProcessor Specification (old) and ACPI specifications. The "CPU side" of MSI (how the "address" and "data" fields are used for 80x86 systems) is covered Intel's Programmer's Reference Manual while the "device side" (detecting the capability, etc) is covered by PCI specs. There's also information for interrupt remapping (needed for virtualisation and x2APIC) in a document called "Intel Virtualization Technology for Directed I/O".

All of this covers what the hardware provides. How you use the hardware (how many IDTs, how IRQ balancing is done, what you use IPIs for and how many you feel like having, ...) is up to the OS developer and isn't documented.

Note that most of this is stuff you can worry about later. For now; you only really need to understand that interrupt vectors will eventually need to be dynamically assigned/allocated (and that a table containing "address of handler" and nothing else won't be flexible enough, even just for exceptions).

More specifically; I'd be tempted to use a static table for exceptions only to build an initial IDT; and then have 3 "overlays" (one for "no PIC", one for "spurious IRQs only" and one for "no IO APIC") where only one of the three "overlays" are merged into the initial IDT; then have some kind of "interrupt vector manager" to allow vector/s to be allocated and freed after that. Of course there'd also be minor special cases on top of that. However; for now you'd only need the static table for exceptions plus the "no IO APIC" overlay (and wouldn't need the other two overlays or the "interrupt vector manager").


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply