///////////////////////////////////////////////////////////////////////// // $Id: apic.cc,v 1.8 2001-10-03 13:10:37 bdenney Exp $ ///////////////////////////////////////////////////////////////////////// // #define NEED_CPU_REG_SHORTCUTS 1 #include "bochs.h" #include #define LOG_THIS this-> bx_generic_apic_c *apic_index[APIC_MAX_ID]; bx_generic_apic_c::bx_generic_apic_c () { id = APIC_UNKNOWN_ID; put("APIC?"); settype(APICLOG); hwreset (); } bx_generic_apic_c::~bx_generic_apic_c () { } // init is called during RESET and when an INIT message is delivered. void bx_generic_apic_c::init () { } void bx_generic_apic_c::set_base (Bit32u newbase) { BX_INFO(("relocate APIC id=%d to %8x", id, newbase)); base_addr = newbase; } void bx_generic_apic_c::set_id (Bit8u newid) { // update apic_index if (id != APIC_UNKNOWN_ID) { BX_ASSERT (id < APIC_MAX_ID); if (apic_index[id] != this) BX_PANIC(("inconsistent APIC id table")); apic_index[id] = NULL; } id = newid; if (apic_index[id] != NULL) BX_PANIC(("duplicate APIC id assigned")); apic_index[id] = this; } char * bx_generic_apic_c::get_name () { BX_PANIC(("get_name called on bx_generic_apic_c base class")); return NULL; } Boolean bx_generic_apic_c::is_selected (Bit32u addr, Bit32u len) { if ((addr & ~0xfff) == get_base ()) { if ((addr & 0xf != 0) || (len != 4)) BX_INFO(("warning: misaligned or wrong-size APIC write")); return true; } return false; } void bx_generic_apic_c::read (Bit32u addr, void *data, unsigned len) { if ((addr & ~0xf) != ((addr+len-1) & ~0xf)) BX_PANIC(("APIC read spans 32-bit boundary")); Bit32u value; read_aligned (addr, &value, 4); if ((addr&3) == 0) { *((Bit32u *)data) = value; return; } // handle partial word read, independent of endian-ness. Bit8u bytes[4]; bytes[0] = value & 0xff; bytes[1] = (value >> 8) & 0xff; bytes[2] = (value >> 16) & 0xff; bytes[3] = (value >> 24) & 0xff; Bit8u *p1 = bytes+(addr&3); Bit8u *p2 = (Bit8u *)data; for (int i=0; imatch_logical_addr(dest)) mask |= (1<get_ppr () < lowest_priority) { lowest_priority = apic->get_ppr (); lowest_mask = 1<= 0); } break; case 5: // INIT { for (int bit=0; bitinit (); } } return true; case 6: // Start Up (local apic only) BX_ASSERT (get_type () == APIC_TYPE_LOCAL_APIC); for (int bit=0; bitstartup_msg (vector); return true; case 2: // SMI case 3: // reserved case 4: // NMI case 7: // ExtINT (I/O apic only) default: BX_PANIC(("APIC delivery mode %d not implemented", delivery_mode)); } // Fixed delivery mode if (bx_dbg.apic) BX_INFO(("delivering vector=0x%02x to bitmask=%04x", (int)vector, deliver_bitmask)); for (int bit=0; bittrigger_irq (vector, id); } } } return true; } bx_local_apic_c::bx_local_apic_c(BX_CPU_C *mycpu) : bx_generic_apic_c () { char buffer[16]; cpu = mycpu; hwreset (); } void bx_local_apic_c::hwreset () { /* same as INIT but also sets arbitration ID and APIC ID */ init (); /* since id is set explicitly by the function that creates the CPU object, do not mess around with it */ // id = APIC_UNKNOWN_ID; arb_id = id; } void bx_local_apic_c::init () { bx_generic_apic_c::init (); BX_INFO(("local apic in %s initializing", (cpu && cpu->name) ? cpu->name : "?")); // default address for a local APIC, can be moved base_addr = 0xfee00000; err_status = 0; log_dest = 0; dest_format = 0xff; for (int bit=0; bitname, "CPU apicid=%02x", (Bit32u)id); if (id >= 0 && id <= 15) { char buffer[16]; sprintf (buffer, "APIC%x", id); put(buffer); settype(CPU0LOG + id); sprintf (buffer, "CPU%x", id); cpu->put (buffer); } else { BX_INFO (("naming convention for apics requires id=0-15 only")); } if(BX_CPU_LEVEL<2) BX_INFO(( "8086" )); else BX_INFO(( "80%d86", BX_CPU_LEVEL )); } char * bx_local_apic_c::get_name() { return cpu->name; } void bx_local_apic_c::set_divide_configuration (Bit32u value) { BX_ASSERT (value == (value & 0x0b)); // move bit 3 down to bit 0. value = ((value & 8) >> 1) | (value & 3); BX_ASSERT (value >= 0 && value <= 7); timer_divide_factor = (value==7)? 1 : (2 << value); if (bx_dbg.apic) BX_INFO(("%s: set timer divide factor to %d", cpu->name, timer_divide_factor)); } void bx_local_apic_c::write (Bit32u addr, Bit32u *data, unsigned len) { assert (len == 4); if (bx_dbg.apic) BX_INFO(("%s: write %08x to APIC address %08x", cpu->name, *data, addr)); //assert (!(addr & 0xf)); addr &= 0xff0; switch (addr) { case 0x20: // local APIC id id = ((*data)>>24) & 0xf; break; case 0x80: // task priority task_priority = *data & 0xff; break; case 0xb0: // EOI { if (bx_dbg.apic) BX_INFO(("%s: Wrote 0x%04x to EOI", cpu->name, *data)); int vec = highest_priority_int (isr); if (vec < 0) { BX_INFO(("EOI written without any bit in ISR")); } else { if (bx_dbg.apic) BX_INFO(("%s: local apic received EOI, hopefully for vector 0x%02x", cpu->name, vec)); isr[vec] = 0; service_local_apic (); } if (bx_dbg.apic) print_status (); } break; case 0xd0: // logical destination log_dest = (*data >> 24) & 0xff; break; case 0xe0: // destination format dest_format = (*data >> 28) & 0xf; break; case 0xf0: // spurious interrupt vector spurious_vec = (spurious_vec & 0x0f) | (*data & 0x3f0); break; case 0x280: // error status reg // Here's what the IA-devguide-3 says on p.7-45: // The ESR is a read/write register and is reset after being written to // by the processor. A write to the ESR must be done just prior to // reading the ESR to allow the register to be updated. // This doesn't seem clear. If the write clears the register, then // wouldn't you always read zero? Otherwise, what does the write do? err_status = 0; break; case 0x300: // interrupt command reg 0-31 { icr_low = *data & ~(1<<12); // force delivery status bit = 0 (idle) int dest = (icr_high >> 24) & 0xff; int trig_mode = (icr_low >> 15) & 1; int level = (icr_low >> 14) & 1; int dest_mode = (icr_low >> 11) & 1; int delivery_mode = (icr_low >> 8) & 7; int vector = (icr_low & 0xff); // // deliver will call get_delivery_bitmask to decide who to send to. // This local_apic class redefines get_delivery_bitmask to // implement the destination shorthand field, which doesn't exist // for all APICs. Boolean accepted = deliver (dest, dest_mode, delivery_mode, vector, level, trig_mode); if (!accepted) err_status |= APIC_ERR_TX_ACCEPT_ERR; } break; case 0x310: // interrupt command reg 31-63 icr_high = *data & 0xff000000; break; case 0x320: // LVT Timer Reg lvt[APIC_LVT_TIMER] = *data & 0x310ff; break; case 0x330: // LVT Thermal Monitor lvt[APIC_LVT_THERMAL] = *data & 0x117ff; break; case 0x340: // LVT Performance Counter lvt[APIC_LVT_PERFORM] = *data & 0x117ff; break; case 0x350: // LVT LINT0 Reg lvt[APIC_LVT_LINT0] = *data & 0x1f7ff; break; case 0x360: // LVT Lint1 Reg lvt[APIC_LVT_LINT1] = *data & 0x1f7ff; break; case 0x370: // LVT Error Reg lvt[APIC_LVT_ERROR] = *data & 0x117ff; break; case 0x380: // initial count for timer timer_initial = *data; // This should trigger the counter to start. If already started, // restart from the new start value. timer_current = timer_initial; timer_active = true; timer_divide_counter = 0; break; case 0x3e0: // timer divide configuration // only bits 3, 1, and 0 are writable timer_divconf = *data & 0xb; set_divide_configuration (timer_divconf); break; /* all read-only registers go here */ case 0x30: // local APIC version case 0x90: // arbitration priority case 0xa0: // processor priority // ISRs not writable case 0x100: case 0x110: case 0x120: case 0x130: case 0x140: case 0x150: case 0x160: case 0x170: // TMRs not writable case 0x180: case 0x190: case 0x1a0: case 0x1b0: case 0x1c0: case 0x1d0: case 0x1e0: case 0x1f0: // IRRs not writable case 0x200: case 0x210: case 0x220: case 0x230: case 0x240: case 0x250: case 0x260: case 0x270: // current count for timer case 0x390: // all read-only registers should fall into this line BX_INFO(("warning: write to read-only APIC register 0x%02x", addr)); break; default: err_status |= APIC_ERR_ILLEGAL_ADDR; // but for now I want to know about it in case I missed some. BX_PANIC(("APIC register %08x not implemented", addr)); } } void bx_local_apic_c::startup_msg (Bit32u vector) { if (cpu->debug_trap & 0x80000000) { cpu->debug_trap &= ~0x80000000; cpu->eip = 0; cpu->load_seg_reg (&cpu->sregs[BX_SEG_REG_CS], vector*0x100); BX_INFO(("%s started up at 0x%x by APIC", cpu->name, cpu->eip)); } else { BX_INFO(("%s started up by APIC, but was not halted at the time", cpu->name)); } } void bx_local_apic_c::read_aligned (Bit32u addr, Bit32u *data, unsigned len) { assert (len == 4); *data = 0; // default value for unimplemented registers Bit32u addr2 = addr & 0xff0; switch (addr2) { case 0x20: // local APIC id *data = (id) << 24; break; case 0x30: // local APIC version *data = 0x00170011; break; case 0x80: // task priority *data = task_priority & 0xff; break; case 0x90: // arbitration priority *data = get_apr (); break; case 0xa0: // processor priority *data = get_ppr (); break; case 0xb0: // EOI BX_PANIC(("EOI register not writable")); break; case 0xd0: // logical destination *data = (log_dest & 0xff) << 24; break; case 0xe0: // destination format *data = ((dest_format & 0xf) << 24) | 0x0fffffff; break; case 0xf0: // spurious interrupt vector *data = spurious_vec; break; // ISRs not writable case 0x100: case 0x110: case 0x120: case 0x130: case 0x140: case 0x150: case 0x160: case 0x170: case 0x180: case 0x190: case 0x1a0: case 0x1b0: case 0x1c0: case 0x1d0: case 0x1e0: case 0x1f0: case 0x200: case 0x210: case 0x220: case 0x230: case 0x240: case 0x250: case 0x260: case 0x270: *data = 0; BX_INFO(("reading ISR,TMR,IRR not implemented")); break; case 0x280: // error status reg *data = err_status; break; case 0x300: // interrupt command reg 0-31 *data = icr_low; break; case 0x310: // interrupt command reg 31-63 *data = icr_high; break; case 0x320: // LVT Timer Reg case 0x330: // LVT Thermal Monitor case 0x340: // LVT Performance Counter case 0x350: // LVT LINT0 Reg case 0x360: // LVT Lint1 Reg case 0x370: // LVT Error Reg { int index = (addr2 - 0x320) >> 4; *data = lvt[index]; break; } case 0x380: // initial count for timer *data = timer_initial; break; case 0x390: // current count for timer *data = timer_current; break; case 0x3e0: // timer divide configuration *data = timer_divconf; break; default: BX_INFO(("APIC register %08x not implemented", addr)); } if (bx_dbg.apic) BX_INFO(("%s: read from APIC address %08x = %08x", cpu->name, addr, *data)); } int bx_local_apic_c::highest_priority_int (Bit8u *array) { for (int i=0; iINTR) return; // INTR already up; do nothing // find first interrupt in irr. int first_irr = highest_priority_int (irr); int first_isr = highest_priority_int (isr); if (first_irr < 0) return; // no interrupts, leave INTR=0 if (first_isr >= 0 && first_irr >= first_isr) { if (bx_dbg.apic) BX_INFO(("local apic (%s): not delivering int%02x because int%02x is in service", cpu->name, first_irr, first_isr)); return; } // interrupt has appeared in irr. raise INTR. When the CPU // acknowledges, we will run highest_priority_int again and // return it. if (bx_dbg.apic) BX_INFO(("service_local_apic(): setting INTR=1 for vector 0x%02x", first_irr)); cpu->set_INTR (1); cpu->int_from_local_apic = 1; } void bx_local_apic_c::trigger_irq (unsigned vector, unsigned from) { if (bx_dbg.apic) BX_INFO(("Local apic on %s: trigger interrupt vector=0x%x", cpu->name, vector)); irr[vector] = 1; service_local_apic (); } void bx_local_apic_c::untrigger_irq (unsigned vector, unsigned from) { if (bx_dbg.apic) BX_INFO(("Local apic on %s: untrigger interrupt vector=0x%x", cpu->name, vector)); // hardware says "no more". clear the bit. If the CPU hasn't yet // acknowledged the interrupt, it will never be serviced. BX_ASSERT (irr[vector] == 1); irr[vector] = 0; if (bx_dbg.apic) print_status (); } Bit8u bx_local_apic_c::acknowledge_int () { // CPU calls this when it is ready to service one interrupt if (!cpu->INTR) BX_PANIC(("%s: acknowledged an interrupt, but INTR=0", cpu->name)); BX_ASSERT (cpu->int_from_local_apic); int vector = highest_priority_int (irr); BX_ASSERT (irr[vector] == 1); if (bx_dbg.apic) BX_INFO(("%s: acknowledge_int returning vector 0x%x", cpu->name, vector)); // currently isr never gets cleared, so no point //BX_ASSERT (isr[vector] == 0); irr[vector] = 0; isr[vector] = 1; if (bx_dbg.apic) { BX_INFO(("Status after setting isr:")); print_status (); } cpu->INTR = 0; cpu->int_from_local_apic = 0; service_local_apic (); // will set INTR again if another is ready return vector; } void bx_local_apic_c::print_status () { BX_INFO(("%s local apic: status is {:", cpu->name)); for (int vec=0; vecname)); } Boolean bx_local_apic_c::match_logical_addr (Bit8u address) { if (dest_format != 0xf) { BX_PANIC(("bx_local_apic_c::match_logical_addr: cluster model addressing not implemented")); } // if all address bits are 1, send to all local APICs. SDG3:7-27. if (address == 0xff) { if (bx_dbg.apic) BX_INFO(("%s: MDA=0xff matches everybody", cpu->name)); return true; } Boolean match = ((address & log_dest) != 0); if (bx_dbg.apic) { BX_INFO(("%s: comparing MDA %02x to my LDR %02x -> %s", cpu->name, address, log_dest, match? "Match" : "Not a match")); } return match; } Bit32u bx_local_apic_c::get_delivery_bitmask (Bit8u dest, Bit8u dest_mode) { int dest_shorthand = (icr_low >> 18) & 3; Bit32u all_mask = (1<get_type () != APIC_TYPE_LOCAL_APIC)) mask &= ~(1<name, usec_delta)); // unless usec_delta is guaranteed to be a multiple of 128, I can't // just divide usec_delta by the divide-down value. Instead, it will // have a similar effect to implement the divide-down by ignoring // some fraction of calls to this function. This can be improved if // more granularity is important. timer_divide_counter = (timer_divide_counter + 1) % timer_divide_factor; if (timer_divide_counter != 0) return; if (timer_current > usec_delta) { timer_current -= usec_delta; //BX_INFO(("%s: local apic timer is now 0x%08x", cpu->name, timer_current)); return; } // timer reached zero since the last call to periodic. Bit32u timervec = lvt[APIC_LVT_TIMER]; if (timervec & 0x20000) { // periodic mode. Always trigger the interrupt when we reach zero. trigger_irq (timervec & 0xff, id); if (timer_initial == 0) { usec_delta = 0; timer_current = 0; } else { // timer_initial might be smaller than usec_delta. I can't trigger // multiple interrupts, so just try to get the timer_current right. while (usec_delta > timer_initial) usec_delta -= timer_initial; timer_current = timer_current + timer_initial - usec_delta; // sanity check. all these are unsigned so I can't check for // negative timer_current. BX_ASSERT ((timer_current + timer_initial) >= usec_delta); } if (bx_dbg.apic) BX_INFO(("%s: local apic timer (periodic) triggered int, reset counter to 0x%08x", cpu->name, timer_current)); } else { // one-shot mode timer_current = 0; if (timer_active) { trigger_irq (timervec & 0xff, id); timer_active = false; if (bx_dbg.apic) BX_INFO(("%s: local apic timer (one-shot) triggered int", cpu->name)); } } }