openblt

a hobby OS from the late 90s
git clone http://frotz.net/git/openblt.git
Log | Files | Refs | LICENSE

smp.c (16117B)


      1 /* $Id: //depot/blt/kernel/smp.c#6 $
      2 **
      3 ** Copyright 1998 Sidney Cammeresi
      4 ** All rights reserved.
      5 ** Copyright (c) 1996, by Steve Passe
      6 ** All rights reserved.
      7 **
      8 ** Redistribution and use in source and binary forms, with or without
      9 ** modification, are permitted provided that the following conditions
     10 ** are met:
     11 ** 1. Redistributions of source code must retain the above copyright
     12 **    notice, this list of conditions, and the following disclaimer.
     13 ** 2. Redistributions in binary form must reproduce the above copyright
     14 **    notice, this list of conditions, and the following disclaimer in the
     15 **    documentation and/or other materials provided with the distribution.
     16 ** 3. The name of the author may not be used to endorse or promote products
     17 **    derived from this software without specific prior written permission.
     18 **
     19 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     20 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     21 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     22 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     23 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     24 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 */
     30 
     31 #include "kernel.h"
     32 #include "i386/io.h"
     33 #include "init.h"
     34 #include "smp.h"
     35 
     36 #undef SMP_DEBUG
     37 
     38 #ifdef SMP_DEBUG
     39 #define SMP_PRINTF(f,a...)     kprintf (f, ## a)
     40 #else
     41 #define SMP_PRINTF(f,a...)
     42 #endif
     43 
     44 const char *cpu_family[] __initdata__ = { "", "", "", "", "Intel 486",
     45 	"Intel Pentium", "Intel Pentium Pro", "Intel Pentium II" };
     46 const unsigned int temp_gdt[] = {0x00000000, 0x00000000,  /* null descriptor */
     47                                  0x0000ffff, 0x00cf9a00,  /* kernel text */
     48                                  0x0000ffff, 0x00cf9200 };/* kernel data */
     49 
     50 char mp_num_def_config, smp_num_cpus = 1, smp_num_running_cpus;
     51 volatile char smp_cpus_ready[BLT_MAX_CPUS], smp_configured = 0, smp_begun = 0;
     52 unsigned int cpuid_max_level, cpuid_eax, cpuid_edx, apic_addr,
     53 	cpu_apic_id[BLT_MAX_CPUS], cpu_os_id[BLT_MAX_CPUS],
     54 	cpu_apic_version[BLT_MAX_CPUS], *apic, *apic_virt, *ioapic;
     55 
     56 mp_flt_ptr *mp_config;
     57 
     58 extern aspace_t *flat;
     59 extern void (*flush) (void);
     60 
     61 volatile char ipi_lock = 0;
     62 
     63 volatile unsigned int apic_read (unsigned int *addr)
     64 {
     65 	return *addr;
     66 }
     67 
     68 void apic_write (unsigned int *addr, unsigned int data)
     69 {
     70 	*addr = data;
     71 }
     72 
     73 /* XXX - hack until we can figure this out */
     74 int bus_clock (void)
     75 {
     76 	return 66000000;
     77 }
     78 
     79 /********** adapted from FreeBSD's i386/i386/mpapic.c **********/
     80 
     81 void __init__ apic_set_timer (int value)
     82 {
     83 	unsigned long lvtt;
     84 	long ticks_per_us;
     85 
     86 	/* calculate divisor and count from value */
     87 	apic_write (APIC_TDCR, APIC_TDCR_1);
     88 	ticks_per_us = bus_clock () / 1000000;
     89 
     90 	/* configure timer as one-shot */
     91 	lvtt = apic_read (APIC_LVTT) & ~(APIC_LVTT_VECTOR | APIC_LVTT_DS |
     92 		APIC_LVTT_M | APIC_LVTT_TM) | APIC_LVTT_M | 0xff;
     93 	apic_write (APIC_LVTT, lvtt);
     94 	apic_write (APIC_ICRT, value * ticks_per_us);
     95 }
     96 
     97 /************************* end FreeBSD *************************/
     98 
     99 int apic_read_timer (void)
    100 {
    101 	return apic_read (APIC_CCRT);
    102 }
    103 
    104 void __init__ u_sleep (int count)
    105 {
    106 	int i;
    107 
    108 	apic_set_timer (count);
    109 	while (i = apic_read (APIC_CCRT)) ;
    110 }
    111 
    112 void __init__ mp_probe (int base, int limit)
    113 {
    114 	unsigned int i, *ptr;
    115 
    116 	for (ptr = (unsigned int *) base; (unsigned int) ptr < limit; ptr++)
    117 		if (*ptr == MP_FLT_SIGNATURE)
    118 		{
    119 			SMP_PRINTF ("smp: found floating pointer structure at %x", ptr);
    120 			mp_config = (mp_flt_ptr *) ptr;
    121 			return;
    122 		}
    123 }
    124 
    125 int __init__ mp_verify_data (void)
    126 {
    127 	char *ptr, total;
    128 	int i;
    129 
    130 	/* check signature */
    131 	if (mp_config->signature != MP_FLT_SIGNATURE)
    132 		return 0;
    133 
    134 	/* compute floating pointer structure checksum */
    135 	ptr = (unsigned char *) mp_config;
    136 	for (i = total = 0; i < (mp_config->mpc_len * 16); i++)
    137 		total += ptr[i];
    138 	if (total)
    139 		return 0;
    140 	kprintf ("smp: CHECKSUMMED");
    141 
    142 	/* compute mp configuration table checksum if we have one*/
    143 	if ((ptr = (unsigned char *) mp_config->mpc) != NULL)
    144 	{
    145 		for (i = total = 0; i < sizeof (mp_config_table); i++)
    146 			total += ptr[i];
    147 		if (total)
    148 			return 0;
    149 	}
    150 	else
    151 		kprintf ("smp: no configuration table\n");
    152 
    153 	return 1;
    154 }
    155 
    156 void __init__ mp_do_config (void)
    157 {
    158 	char *ptr;
    159 	int i;
    160 	mp_ext_pe *pe;
    161 	mp_ext_ioapic *io;
    162 
    163 	/*
    164 	 * we are not running in standard configuration, so we have to look through
    165 	 * all of the mp configuration table crap to figure out how many processors
    166 	 * we have, where our apics are, etc.
    167 	 */
    168 	smp_num_cpus = 0;
    169 
    170 	/* print out our new found configuration. */
    171 	ptr = (char *) &(mp_config->mpc->oem[0]);
    172 	kprintf ("smp: oem id: %c%c%c%c%c%c%c%c product id: "
    173 		"%c%c%c%c%c%c%c%c%c%c%c%c", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4],
    174 		ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12],
    175 		ptr[13], ptr[14], ptr[15], ptr[16], ptr[17], ptr[18], ptr[19],
    176 		ptr[20]);
    177 	SMP_PRINTF ("smp: base table has %d entries, extended section %d bytes",
    178 		mp_config->mpc->num_entries, mp_config->mpc->ext_len);
    179 	apic = (unsigned int *) mp_config->mpc->apic;
    180 
    181 	ptr = (char *) ((unsigned int) mp_config->mpc + sizeof (mp_config_table));
    182 	for (i = 0; i < mp_config->mpc->num_entries; i++)
    183 		switch (*ptr)
    184 		{
    185 			case MP_EXT_PE:
    186 				pe = (mp_ext_pe *) ptr;
    187 				cpu_apic_id[smp_num_cpus] = pe->apic_id;
    188 				cpu_os_id[pe->apic_id] = smp_num_cpus;
    189 				cpu_apic_version [smp_num_cpus] = pe->apic_version;
    190 				kprintf ("smp: cpu#%d: %s, apic id %d, version %d%s",
    191 					smp_num_cpus++, cpu_family[(pe->signature & 0xf00) >> 8],
    192 					pe->apic_id, pe->apic_version, (pe->cpu_flags & 0x2) ?
    193 					", BSP" : "");
    194 				ptr += 20;
    195 				break;
    196 			case MP_EXT_BUS:
    197 				ptr += 8;
    198 				break;
    199 			case MP_EXT_IO_APIC:
    200 				io = (mp_ext_ioapic *) ptr;
    201 				ioapic = io->addr;
    202 				SMP_PRINTF ("smp: found io apic with apic id %d, version %d",
    203 					io->ioapic_id, io->ioapic_version);
    204 				ptr += 8;
    205 				break;
    206 			case MP_EXT_IO_INT:
    207 				ptr += 8;
    208 				break;
    209 			case MP_EXT_LOCAL_INT:
    210 				ptr += 8;
    211 				break;
    212 		}
    213 
    214 	kprintf ("smp: apic @ 0x%x, i/o apic @ 0x%x, total %d processors detected",
    215 		(unsigned int) apic, (unsigned int) ioapic, smp_num_cpus);
    216 }
    217 
    218 /* FIXME - this is a very long stretch that needs to be broken up. */
    219 void __init__ smp_init (void)
    220 {
    221 	unsigned char *ptr, init_val;
    222 	unsigned int i, j, config, num_startups, *new_stack, *dir, *table, *page,
    223 		*common;
    224 
    225 	SMP_PRINTF ("smp: initialising");
    226 	/*
    227 	 * first let's check the kind of processor this is since only intel chips
    228 	 * support the MP specification.
    229 	 */
    230 #if 0
    231 	/* FIXME - this check doesn't work */
    232 	if (!smp_check_cpu ())
    233 		SMP_ERROR ("smp: processor is not Genuine Intel or is a 386 or 486\n");
    234 #else
    235 	j = smp_check_cpu ();
    236 	SMP_PRINTF ("smp: cpu is %x", j);
    237 #endif
    238 
    239 	/*
    240 	 * scan the three spec-defined regions for the floating pointer
    241 	 * structure.  if we find one, copy its address into mp_config,
    242 	 * otherwise, abort smp initialisation.  after that, check the
    243 	 * integrity of the structures.
    244 	 */
    245 	mp_config = (mp_flt_ptr *) 0x1000; /* small hack */
    246 	/* mp_probe (0, 0x400); */
    247 	mp_probe (0x9fc00, 0xa0000);
    248 	mp_probe (0xf0000, 0x100000);
    249 	if (mp_config == (mp_flt_ptr *) 0x1000) /* if unchanged */
    250 		SMP_ERROR ("smp: no floating pointer structure detected\n");
    251 
    252 #if 0
    253 	/* FIXME - this check doesn't work */
    254 	if (!smp_verify_data ())
    255 		SMP_ERROR ("smp: bad configuration information\n");
    256 	kprintf ("smp: it's all good\n");
    257 #endif
    258 
    259 	/*
    260 	 * whee, we're running on an smp machine with valid configuration
    261 	 * information.  print out some of this new intelligence.
    262 	 */
    263 	kprintf ("smp: intel mp version %s, %s", (mp_config->mp_rev == 1) ? "1.1" :
    264 		"1.4", (mp_config->mp_feature_2 & 0x80) ?
    265 		"imcr and pic compatibility mode." : "virtual wire compatibility mode.");
    266 
    267 	if (!mp_config->mpc)
    268 	{
    269 		/* this system conforms to one of the default configurations */
    270 		mp_num_def_config = mp_config->mp_feature_1;
    271 		kprintf ("smp: standard configuration %d", mp_num_def_config);
    272 		smp_num_cpus = 2;
    273 		cpu_apic_id[0] = 0;
    274 		cpu_apic_id[1] = 1;
    275 		apic = (unsigned int *) 0xfee00000;
    276 		ioapic = (unsigned int *) 0xfec00000;
    277 		kprintf ("smp: WARNING: standard configuration code is untested");
    278 	else
    279 	{
    280 		/*
    281 		 * we don't have a default configuration, so now we have to locate all
    282 		 * of our hardware.  boy, do we have a lot of work to do.
    283 		 */
    284 		SMP_PRINTF ("smp: not a standard configuration");
    285 		mp_num_def_config = 0;
    286 		mp_do_config ();
    287 	}
    288 	smp_configured = 1;
    289 
    290 	/* set up the apic */
    291 	aspace_maphi (flat, 0xfee00, 0x3fc, 1, 0x13);
    292 	apic_virt = (unsigned int *) 0x803fc000;
    293 	asm ("mov %0, %%cr3" : : "r" (_cr3));
    294 
    295 	config = apic_read ((unsigned int *) APIC_SIVR) & APIC_FOCUS | APIC_ENABLE |
    296 		0xff; /* set spurious interrupt vector to 0xff */
    297 	apic_write (APIC_SIVR, config);
    298 	config = apic_read (APIC_TPRI) & 0xffffff00; /* accept all interrupts */
    299 	apic_write (APIC_TPRI, config);
    300 
    301 	apic_read (APIC_SIVR);
    302 	apic_write (APIC_EOI, 0);
    303 
    304 	config = apic_read (APIC_LVT3) & 0xffffff00 | 0xfe; /* XXX - set vector */
    305 	apic_write (APIC_LVT3, config);
    306 
    307 	/* grab a page at 0x9000 for communicating with the aps */
    308 	aspace_map (flat, 0x9, 0x9, 1, 3);
    309 	common = (unsigned int *) 0x9000;
    310 	for (i = 0; i < 6; i++)
    311 		common [i + 1] = temp_gdt [i];
    312 	*((unsigned int *) 0x9024) = _cr3;
    313 	aspace_map (flat, 0xa, 0xa, 1, 3);
    314 	ptr = (unsigned char *) 0xa000;
    315 	for (j = 0; j < ((unsigned int) &trampoline_end - (unsigned int)
    316 			&trampoline + 1); j++)
    317 		ptr[j] = ((unsigned char *) &trampoline)[j];
    318 
    319 	/*
    320 	 * okay, we're ready to go.  boot all of the ap's now.  we loop through
    321 	 * using the kernel pe id numbers which were set up in smp_do_config ()
    322 	 *
    323 	 * XXX - we assume the bsp is the first detected.  trying to boot it here
    324 	 * would be, ahem, bad.
    325 	 */
    326 	for (i = 1; i < smp_num_cpus; i++)
    327 	{
    328 		kprintf ("smp: booting cpu#%d with stack 0x%x", i, i * 0x1000);
    329 
    330 		/* map stacks and copy bootstrap code onto them */
    331 		aspace_map (flat, i, i, 1, 3);
    332 		ptr = (unsigned char *) (0x1000 * i);
    333 		for (j = 0; j < ((unsigned int) &trampoline_end - (unsigned int)
    334 				&trampoline + 1); j++)
    335 			ptr[j] = ((unsigned char *) &trampoline)[j];
    336 
    337 		/*
    338 		 * modify the bootstrap code, specifically, the third highest order
    339 		 * byte of the ljmp.  we need to do this if we have more than two
    340 		 * processors, i.e. more than one ap.
    341 		 */
    342 		ptr = (char *) flush;
    343 		*(ptr - 5) = i << 4;
    344 
    345 		/* write the location of the stack into a fixed spot in memory */
    346 		*common = 0x1000 * i;
    347 
    348 		/* set shutdown code and warm reset vector */
    349 		ptr = (unsigned char *) 0xf;
    350 		*ptr = 0xa;
    351 		ptr = (unsigned char *) 0x467;
    352 		*ptr = 0x1000 * i;
    353 		ptr = (unsigned char *) 0x469;
    354 		*ptr = 0;
    355 
    356 		/* reset cpu ready bit */
    357 		smp_cpus_ready[i] = 0;
    358 
    359 		/* clear apic errors */
    360 		if (cpu_apic_version[i] & 0xf0)
    361 		{
    362 			apic_write (APIC_ESR, 0);
    363 			apic_read (APIC_ESR);
    364 		}
    365 
    366 		/* send (aka assert) INIT IPI */
    367 		SMP_PRINTF ("smp: asserting INIT");
    368 		config = apic_read (APIC_ICR2) & 0xf0ffffff | (cpu_apic_id[i] << 24);
    369 		apic_write (APIC_ICR2, config); /* set target pe */
    370 		config = apic_read (APIC_ICR1) & 0xfff0f800 | APIC_DM_INIT | 
    371 			APIC_LEVEL_TRIG | APIC_ASSERT;
    372 		apic_write (APIC_ICR1, config);
    373 
    374 		/* deassert INIT */
    375 		SMP_PRINTF ("smp: deasserting INIT");
    376 		config = apic_read (APIC_ICR2) & 0xffffff | (cpu_apic_id[i] << 24);
    377 		apic_write (APIC_ICR2, config);
    378 		config = apic_read (APIC_ICR1) & ~0xcdfff | APIC_LEVEL_TRIG |
    379 			APIC_DM_INIT;
    380 
    381 		/* wait 10ms */
    382 		u_sleep (10000);
    383 
    384 		/* is this a local apic or an 82489dx ? */
    385 		num_startups = (cpu_apic_version[i] & 0xf0) ? 2 : 0;
    386 		for (j = 0; j < num_startups; j++)
    387 		{
    388 			/* it's a local apic, so send STARTUP IPIs */
    389 			SMP_PRINTF ("smp: sending STARTUP");
    390 			apic_write (APIC_ESR, 0);
    391 
    392 			/* set target pe */
    393 			config = apic_read (APIC_ICR2) & 0xf0ffffff | (cpu_apic_id[i] <<
    394 				24);
    395 			apic_write (APIC_ICR2, config);
    396 
    397 			/* send the IPI */
    398 			config = apic_read (APIC_ICR1) & 0xfff0f800 | APIC_DM_STARTUP |
    399 				((0x1000 * i) >> 12);
    400 			apic_write (APIC_ICR1, config);
    401 
    402 			/* wait */
    403 			u_sleep (200);
    404 			while (apic_read (APIC_ICR1) & 0x1000) ;
    405 		}
    406 
    407 		/* wait for processor to boot */
    408 		SMP_PRINTF ("smp: waiting for cpu#%d to come online", i);
    409 		apic_set_timer (5000000); /* 5 seconds */
    410 		while (apic_read_timer ())
    411 			if (smp_cpus_ready[i])
    412 				break;
    413 		if (!smp_cpus_ready[i])
    414 			kprintf ("smp: initialisation of cpu#%d failed", i);
    415 	}
    416 
    417 	/* this processor is already booted */
    418 	smp_cpus_ready[smp_my_cpu ()] = 1;
    419 
    420 	//ioapic_init ();
    421 	//apic_write (APIC_LVTT, 0x100fe);
    422 	//ipi_all_but_self (0x40);
    423 }
    424 
    425 void __init__ smp_cpu_setup (void)
    426 {
    427 	unsigned int config;
    428 
    429 	/* load the correct values into the idtr and gdtr */
    430 	i386lidt ((uint32) idt, 0x3ff);
    431 	i386lgdt ((uint32) gdt, 0x400 / 8);
    432 
    433 	/* set up the local apic */
    434 	config = apic_read ((unsigned int *) APIC_SIVR) & APIC_FOCUS | APIC_ENABLE |
    435 		0xff; /* set spurious interrupt vector to 0xff */
    436 	apic_write (APIC_SIVR, config);
    437 	config = apic_read (APIC_TPRI) & 0xffffff00; /* accept all interrupts */
    438 	apic_write (APIC_TPRI, config);
    439 
    440 	apic_read (APIC_SIVR);
    441 	apic_write (APIC_EOI, 0);
    442 
    443 	config = apic_read (APIC_LVT3) & 0xffffff00; /* XXX - set vector */
    444 	apic_write (APIC_LVT3, config);
    445 }
    446 
    447 void __init__ smp_final_setup (void)
    448 {
    449 	unsigned int config, ticks_per_us;
    450 
    451 	ticks_per_us = bus_clock () / 1000000;
    452 	apic_write (APIC_ICRT, ticks_per_us * 10000); /* 10 ms */
    453 	config = 0x20030;
    454 	apic_write (APIC_LVTT, config);
    455 
    456 	if (smp_my_cpu ())
    457 		sti ();
    458 	else
    459 		mask_irq (0);
    460 }
    461 
    462 void __init__ smp_cpu_ready (void)
    463 {
    464 	/*
    465 	 * C code entry point for aps.  we finish initialisation and wait for the
    466 	 * signal from the bsp before heading off into the wild blue yonder.
    467 	 */
    468 
    469 	/* finish processor initialisation. */
    470 	smp_cpu_setup ();
    471 
    472 	/* inform the world of our presence. */
    473 	kprintf ("smp: cpu#%d is online", smp_my_cpu ());
    474 	smp_cpus_ready[smp_my_cpu ()] = 1;
    475 
    476 	/* wait for signal from bsp. */
    477 	while (!smp_begun) ;
    478 	kprintf ("smp: cpu#%d running", smp_my_cpu ());
    479 
    480 	/* set up local apic to generate timer interrupts. */
    481 	smp_final_setup ();
    482 
    483 	/* bon voyage. */
    484 	//swtch ();
    485 	while (1) ;
    486 }
    487 
    488 void __init__ smp_begin (void)
    489 {
    490 	/*
    491 	 * begin smp.  we set the begun bit to release the application processors
    492 	 * out of their cages.  they storm out, fly into the scheduler, and off
    493 	 * we go.
    494 	 */
    495 	smp_begun = 1;
    496 }
    497 
    498 int smp_my_cpu (void)
    499 {
    500 	if (!smp_configured)
    501 		return 0;
    502 	else
    503 		return cpu_os_id [(apic_read (APIC_ID) & 0xffffffff) >> 24];
    504 }
    505 
    506 void ipi_dest (int num, int pe)
    507 {
    508 	int config;
    509 
    510 	p (&ipi_lock);
    511 	cli ();
    512 	config = apic_read (APIC_ICR2) & 0xffffff | (pe << 24);
    513 	apic_write (APIC_ICR2, config);
    514 	config = apic_read (APIC_ICR1) & 0xfff0f800 | APIC_DEST_FIELD | num |
    515 		0x4000;
    516 	apic_write (APIC_ICR1, config);
    517 	sti ();
    518 	v (&ipi_lock);
    519 }
    520 
    521 void ipi_self (int num)
    522 {
    523 	int config;
    524 
    525 	p (&ipi_lock);
    526 	cli ();
    527 	config = apic_read (APIC_ICR2) & 0xffffff;
    528 	apic_write (APIC_ICR2, config);
    529 	config = apic_read (APIC_ICR1) & ~0xfdfff | APIC_DEST_SELF | num;
    530 	apic_write (APIC_ICR1, config);
    531 	sti ();
    532 	v (&ipi_lock);
    533 }
    534 
    535 void ipi_all (int num)
    536 {
    537 	int config;
    538 
    539 	p (&ipi_lock);
    540 	cli ();
    541 	config = apic_read (APIC_ICR2) & 0xffffff;
    542 	apic_write (APIC_ICR2, config);
    543 	config = apic_read (APIC_ICR1) & ~0xfdfff | APIC_DEST_ALL | num;
    544 	apic_write (APIC_ICR1, config);
    545 	sti ();
    546 	v (&ipi_lock);
    547 }
    548 
    549 void ipi_all_but_self (int num)
    550 {
    551 	int config;
    552 
    553 	p (&ipi_lock);
    554 	cli ();
    555 	config = apic_read (APIC_ICR2) & 0xffffff;
    556 	apic_write (APIC_ICR2, config);
    557 	config = apic_read (APIC_ICR1) & ~0xfdfff | APIC_DEST_ALL_BUT_SELF | num;
    558 	apic_write (APIC_ICR1, config);
    559 	sti ();
    560 	v (&ipi_lock);
    561 }
    562 
    563 volatile unsigned int ioapic_read (unsigned int offset)
    564 {
    565 	*ioapic = offset;
    566 	return *(ioapic + 4); /* ioapic + 16 bytes */
    567 }
    568 
    569 void ioapic_write (unsigned int offset, unsigned int data)
    570 {
    571 	*ioapic = offset;
    572 	*(ioapic + 4) = data; /* ioapic + 16 bytes */
    573 }
    574 
    575 void ioapic_init (void)
    576 {
    577 }
    578 
    579 void apic_eoi (void)
    580 {
    581 	apic_write (APIC_EOI, 0);
    582 }
    583