URL
https://opencores.org/ocsvn/or1k_old/or1k_old/trunk
Subversion Repositories or1k_old
Compare Revisions
- This comparison shows the changes necessary to convert path
/or1k_old/trunk/rc203soc/sw/uClinux/arch/i386/kernel
- from Rev 1765 to Rev 1782
- ↔ Reverse comparison
Rev 1765 → Rev 1782
/time.c
0,0 → 1,505
/* |
* linux/arch/i386/kernel/time.c |
* |
* Copyright (C) 1991, 1992, 1995 Linus Torvalds |
* |
* This file contains the PC-specific time handling details: |
* reading the RTC at bootup, etc.. |
* 1994-07-02 Alan Modra |
* fixed set_rtc_mmss, fixed time.year for >= 2000, new mktime |
* 1995-03-26 Markus Kuhn |
* fixed 500 ms bug at call to set_rtc_mmss, fixed DS12887 |
* precision CMOS clock update |
* 1996-05-03 Ingo Molnar |
* fixed time warps in do_[slow|fast]_gettimeoffset() |
* 1997-09-10 Updated NTP code according to technical memorandum Jan '96 |
* "A Kernel Model for Precision Timekeeping" by Dave Mills |
*/ |
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/param.h> |
#include <linux/string.h> |
#include <linux/mm.h> |
#include <linux/interrupt.h> |
#include <linux/time.h> |
#include <linux/delay.h> |
|
#include <asm/segment.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/delay.h> |
|
#include <linux/mc146818rtc.h> |
#include <linux/timex.h> |
#include <linux/config.h> |
|
extern int setup_x86_irq(int, struct irqaction *); |
|
#ifndef CONFIG_APM /* cycle counter may be unreliable */ |
/* Cycle counter value at the previous timer interrupt.. */ |
static struct { |
unsigned long low; |
unsigned long high; |
} init_timer_cc, last_timer_cc; |
|
/* |
* This is more assembly than C, but it's also rather |
* timing-critical and we have to use assembler to get |
* reasonable 64-bit arithmetic |
*/ |
static unsigned long do_fast_gettimeoffset(void) |
{ |
register unsigned long eax asm("ax"); |
register unsigned long edx asm("dx"); |
unsigned long tmp, quotient, low_timer, missing_time; |
|
/* Last jiffy when do_fast_gettimeoffset() was called.. */ |
static unsigned long last_jiffies=0; |
|
/* Cached "clocks per usec" value.. */ |
static unsigned long cached_quotient=0; |
|
/* The "clocks per usec" value is calculated once each jiffy */ |
tmp = jiffies; |
quotient = cached_quotient; |
low_timer = last_timer_cc.low; |
missing_time = 0; |
if (last_jiffies != tmp) { |
last_jiffies = tmp; |
/* |
* test for hanging bottom handler (this means xtime is not |
* updated yet) |
*/ |
if (test_bit(TIMER_BH, &bh_active) ) |
{ |
missing_time = 1000020/HZ; |
} |
|
/* Get last timer tick in absolute kernel time */ |
eax = low_timer; |
edx = last_timer_cc.high; |
__asm__("subl "SYMBOL_NAME_STR(init_timer_cc)",%0\n\t" |
"sbbl "SYMBOL_NAME_STR(init_timer_cc)"+4,%1" |
:"=a" (eax), "=d" (edx) |
:"0" (eax), "1" (edx)); |
|
/* |
* Divide the 64-bit time with the 32-bit jiffy counter, |
* getting the quotient in clocks. |
* |
* Giving quotient = "average internal clocks per usec" |
*/ |
__asm__("divl %2" |
:"=a" (eax), "=d" (edx) |
:"r" (tmp), |
"0" (eax), "1" (edx)); |
|
edx = 1000020/HZ; |
tmp = eax; |
eax = 0; |
|
__asm__("divl %2" |
:"=a" (eax), "=d" (edx) |
:"r" (tmp), |
"0" (eax), "1" (edx)); |
cached_quotient = eax; |
quotient = eax; |
} |
|
/* Read the time counter */ |
__asm__(".byte 0x0f,0x31" |
:"=a" (eax), "=d" (edx)); |
|
/* .. relative to previous jiffy (32 bits is enough) */ |
edx = 0; |
eax -= low_timer; |
|
/* |
* Time offset = (1000020/HZ * time_low) / quotient. |
*/ |
|
__asm__("mull %2" |
:"=a" (eax), "=d" (edx) |
:"r" (quotient), |
"0" (eax), "1" (edx)); |
|
/* |
* Due to rounding errors (and jiffies inconsistencies), |
* we need to check the result so that we'll get a timer |
* that is monotonic. |
*/ |
if (edx >= 1000020/HZ) |
edx = 1000020/HZ-1; |
|
eax = edx + missing_time; |
return eax; |
} |
#endif |
|
/* This function must be called with interrupts disabled |
* It was inspired by Steve McCanne's microtime-i386 for BSD. -- jrs |
* |
* However, the pc-audio speaker driver changes the divisor so that |
* it gets interrupted rather more often - it loads 64 into the |
* counter rather than 11932! This has an adverse impact on |
* do_gettimeoffset() -- it stops working! What is also not |
* good is that the interval that our timer function gets called |
* is no longer 10.0002 ms, but 9.9767 ms. To get around this |
* would require using a different timing source. Maybe someone |
* could use the RTC - I know that this can interrupt at frequencies |
* ranging from 8192Hz to 2Hz. If I had the energy, I'd somehow fix |
* it so that at startup, the timer code in sched.c would select |
* using either the RTC or the 8253 timer. The decision would be |
* based on whether there was any other device around that needed |
* to trample on the 8253. I'd set up the RTC to interrupt at 1024 Hz, |
* and then do some jiggery to have a version of do_timer that |
* advanced the clock by 1/1024 s. Every time that reached over 1/100 |
* of a second, then do all the old code. If the time was kept correct |
* then do_gettimeoffset could just return 0 - there is no low order |
* divider that can be accessed. |
* |
* Ideally, you would be able to use the RTC for the speaker driver, |
* but it appears that the speaker driver really needs interrupt more |
* often than every 120 us or so. |
* |
* Anyway, this needs more thought.... pjsg (1993-08-28) |
* |
* If you are really that interested, you should be reading |
* comp.protocols.time.ntp! |
*/ |
|
#define TICK_SIZE tick |
|
static unsigned long do_slow_gettimeoffset(void) |
{ |
int count; |
static int count_p = 0; |
unsigned long offset = 0; |
static unsigned long jiffies_p = 0; |
|
/* |
* cache volatile jiffies temporarily; we have IRQs turned off. |
*/ |
unsigned long jiffies_t; |
|
/* timer count may underflow right here */ |
outb_p(0x00, 0x43); /* latch the count ASAP */ |
count = inb_p(0x40); /* read the latched count */ |
count |= inb(0x40) << 8; |
|
jiffies_t = jiffies; |
|
/* |
* avoiding timer inconsistencies (they are rare, but they happen)... |
* there are three kinds of problems that must be avoided here: |
* 1. the timer counter underflows |
* 2. hardware problem with the timer, not giving us continuous time, |
* the counter does small "jumps" upwards on some Pentium systems, |
* thus causes time warps |
* 3. we are after the timer interrupt, but the bottom half handler |
* hasn't executed yet. |
*/ |
if( count > count_p ) { |
if( jiffies_t == jiffies_p ) { |
if( count > LATCH-LATCH/100 ) |
offset = TICK_SIZE; |
else |
/* |
* argh, the timer is bugging we cant do nothing |
* but to give the previous clock value. |
*/ |
count = count_p; |
} else { |
if( test_bit(TIMER_BH, &bh_active) ) { |
/* |
* we have detected a counter underflow. |
*/ |
offset = TICK_SIZE; |
count_p = count; |
} else { |
count_p = count; |
jiffies_p = jiffies_t; |
} |
} |
} else { |
count_p = count; |
jiffies_p = jiffies_t; |
} |
|
|
count = ((LATCH-1) - count) * TICK_SIZE; |
count = (count + LATCH/2) / LATCH; |
|
return offset + count; |
} |
|
static unsigned long (*do_gettimeoffset)(void) = do_slow_gettimeoffset; |
|
/* |
* This version of gettimeofday has near microsecond resolution. |
*/ |
void do_gettimeofday(struct timeval *tv) |
{ |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
*tv = xtime; |
tv->tv_usec += do_gettimeoffset(); |
if (tv->tv_usec >= 1000000) { |
tv->tv_usec -= 1000000; |
tv->tv_sec++; |
} |
restore_flags(flags); |
} |
|
void do_settimeofday(struct timeval *tv) |
{ |
cli(); |
/* This is revolting. We need to set the xtime.tv_usec |
* correctly. However, the value in this location is |
* is value at the last tick. |
* Discover what correction gettimeofday |
* would have done, and then undo it! |
*/ |
tv->tv_usec -= do_gettimeoffset(); |
|
if (tv->tv_usec < 0) { |
tv->tv_usec += 1000000; |
tv->tv_sec--; |
} |
|
xtime = *tv; |
time_adjust = 0; /* stop active adjtime() */ |
time_status |= STA_UNSYNC; |
time_state = TIME_ERROR; /* p. 24, (a) */ |
time_maxerror = NTP_PHASE_LIMIT; |
time_esterror = NTP_PHASE_LIMIT; |
sti(); |
} |
|
|
/* |
* In order to set the CMOS clock precisely, set_rtc_mmss has to be |
* called 500 ms after the second nowtime has started, because when |
* nowtime is written into the registers of the CMOS clock, it will |
* jump to the next second precisely 500 ms later. Check the Motorola |
* MC146818A or Dallas DS12887 data sheet for details. |
* |
* BUG: This routine does not handle hour overflow properly; it just |
* sets the minutes. Usually you'll only notice that after reboot! |
*/ |
static int set_rtc_mmss(unsigned long nowtime) |
{ |
int retval = 0; |
int real_seconds, real_minutes, cmos_minutes; |
unsigned char save_control, save_freq_select; |
|
save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */ |
CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL); |
|
save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */ |
CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT); |
|
cmos_minutes = CMOS_READ(RTC_MINUTES); |
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) |
BCD_TO_BIN(cmos_minutes); |
|
/* |
* since we're only adjusting minutes and seconds, |
* don't interfere with hour overflow. This avoids |
* messing with unknown time zones but requires your |
* RTC not to be off by more than 15 minutes |
*/ |
real_seconds = nowtime % 60; |
real_minutes = nowtime / 60; |
if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1) |
real_minutes += 30; /* correct for half hour time zone */ |
real_minutes %= 60; |
|
if (abs(real_minutes - cmos_minutes) < 30) { |
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { |
BIN_TO_BCD(real_seconds); |
BIN_TO_BCD(real_minutes); |
} |
CMOS_WRITE(real_seconds,RTC_SECONDS); |
CMOS_WRITE(real_minutes,RTC_MINUTES); |
} else { |
printk(KERN_WARNING |
"set_rtc_mmss: can't update from %d to %d\n", |
cmos_minutes, real_minutes); |
retval = -1; |
} |
|
/* The following flags have to be released exactly in this order, |
* otherwise the DS12887 (popular MC146818A clone with integrated |
* battery and quartz) will not reset the oscillator and will not |
* update precisely 500 ms later. You won't find this mentioned in |
* the Dallas Semiconductor data sheets, but who believes data |
* sheets anyway ... -- Markus Kuhn |
*/ |
CMOS_WRITE(save_control, RTC_CONTROL); |
CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); |
|
return retval; |
} |
|
/* last time the cmos clock got updated */ |
static long last_rtc_update = 0; |
|
/* |
* timer_interrupt() needs to keep up the real-time clock, |
* as well as call the "do_timer()" routine every clocktick |
*/ |
static inline void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
{ |
do_timer(regs); |
|
/* |
* If we have an externally synchronized Linux clock, then update |
* CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be |
* called as close as possible to 500 ms before the new second starts. |
*/ |
if ((time_status & STA_UNSYNC) == 0 && |
xtime.tv_sec > last_rtc_update + 660 && |
xtime.tv_usec > 500000 - (tick >> 1) && |
xtime.tv_usec < 500000 + (tick >> 1)) |
if (set_rtc_mmss(xtime.tv_sec) == 0) |
last_rtc_update = xtime.tv_sec; |
else |
last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */ |
/* As we return to user mode fire off the other CPU schedulers.. this is |
basically because we don't yet share IRQ's around. This message is |
rigged to be safe on the 386 - basically it's a hack, so don't look |
closely for now.. */ |
/*smp_message_pass(MSG_ALL_BUT_SELF, MSG_RESCHEDULE, 0L, 0); */ |
|
} |
|
#ifndef CONFIG_APM /* cycle counter may be unreliable */ |
/* |
* This is the same as the above, except we _also_ save the current |
* cycle counter value at the time of the timer interrupt, so that |
* we later on can estimate the time of day more exactly. |
*/ |
static void pentium_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
{ |
/* read Pentium cycle counter */ |
__asm__(".byte 0x0f,0x31" |
:"=a" (last_timer_cc.low), |
"=d" (last_timer_cc.high)); |
timer_interrupt(irq, NULL, regs); |
} |
#endif |
|
/* Converts Gregorian date to seconds since 1970-01-01 00:00:00. |
* Assumes input in normal date format, i.e. 1980-12-31 23:59:59 |
* => year=1980, mon=12, day=31, hour=23, min=59, sec=59. |
* |
* [For the Julian calendar (which was used in Russia before 1917, |
* Britain & colonies before 1752, anywhere else before 1582, |
* and is still in use by some communities) leave out the |
* -year/100+year/400 terms, and add 10.] |
* |
* This algorithm was first published by Gauss (I think). |
* |
* WARNING: this function will overflow on 2106-02-07 06:28:16 on |
* machines were long is 32-bit! (However, as time_t is signed, we |
* will already get problems at other places on 2038-01-19 03:14:08) |
*/ |
static inline unsigned long mktime(unsigned int year, unsigned int mon, |
unsigned int day, unsigned int hour, |
unsigned int min, unsigned int sec) |
{ |
if (0 >= (int) (mon -= 2)) { /* 1..12 -> 11,12,1..10 */ |
mon += 12; /* Puts Feb last since it has leap day */ |
year -= 1; |
} |
return ((( |
(unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) + |
year*365 - 719499 |
)*24 + hour /* now have hours */ |
)*60 + min /* now have minutes */ |
)*60 + sec; /* finally seconds */ |
} |
|
/* not static: needed by APM */ |
unsigned long get_cmos_time(void) |
{ |
unsigned int year, mon, day, hour, min, sec; |
int i; |
|
/* The Linux interpretation of the CMOS clock register contents: |
* When the Update-In-Progress (UIP) flag goes from 1 to 0, the |
* RTC registers show the second which has precisely just started. |
* Let's hope other operating systems interpret the RTC the same way. |
*/ |
/* read RTC exactly on falling edge of update flag */ |
for (i = 0 ; i < 1000000 ; i++) /* may take up to 1 second... */ |
if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) |
break; |
for (i = 0 ; i < 1000000 ; i++) /* must try at least 2.228 ms */ |
if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)) |
break; |
do { /* Isn't this overkill ? UIP above should guarantee consistency */ |
sec = CMOS_READ(RTC_SECONDS); |
min = CMOS_READ(RTC_MINUTES); |
hour = CMOS_READ(RTC_HOURS); |
day = CMOS_READ(RTC_DAY_OF_MONTH); |
mon = CMOS_READ(RTC_MONTH); |
year = CMOS_READ(RTC_YEAR); |
} while (sec != CMOS_READ(RTC_SECONDS)); |
if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD) |
{ |
BCD_TO_BIN(sec); |
BCD_TO_BIN(min); |
BCD_TO_BIN(hour); |
BCD_TO_BIN(day); |
BCD_TO_BIN(mon); |
BCD_TO_BIN(year); |
} |
if ((year += 1900) < 1970) |
year += 100; |
return mktime(year, mon, day, hour, min, sec); |
} |
|
static struct irqaction irq0 = { timer_interrupt, 0, 0, "timer", NULL, NULL}; |
|
void time_init(void) |
{ |
xtime.tv_sec = get_cmos_time(); |
xtime.tv_usec = 0; |
|
/* If we have the CPU hardware time counters, use them */ |
#ifndef CONFIG_APM |
/* Don't use them if a suspend/resume could |
corrupt the timer value. This problem |
needs more debugging. */ |
if (x86_capability & 16) |
if (strncmp(x86_vendor_id, "Cyrix", 5) != 0) { |
do_gettimeoffset = do_fast_gettimeoffset; |
|
if( strcmp( x86_vendor_id, "AuthenticAMD" ) == 0 ) { |
if( x86 == 5 ) { |
if( x86_model == 0 ) { |
/* turn on cycle counters during power down */ |
__asm__ __volatile__ (" movl $0x83, %%ecx \n \ |
.byte 0x0f,0x32 \n \ |
orl $1,%%eax \n \ |
.byte 0x0f,0x30 \n " |
: : : "ax", "cx", "dx" ); |
udelay(500); |
} |
} |
} |
|
/* read Pentium cycle counter */ |
__asm__(".byte 0x0f,0x31" |
:"=a" (init_timer_cc.low), |
"=d" (init_timer_cc.high)); |
irq0.handler = pentium_timer_interrupt; |
} |
#endif |
setup_x86_irq(0, &irq0); |
} |
/trampoline32.S
0,0 → 1,20
! |
! 32bit side of the trampoline code |
! |
#define __ASSEMBLY__ |
#include <asm/segment.h> |
! |
! |
! Anything but a relative address here will be wrong by 8K... |
! |
.globl startup32 |
.text |
startup32: |
! Run the kernel |
mov eax,#KERNEL_DS |
mov ds,ax |
mov eax,#0xA5A5A5A5 |
mov [8192],eax |
jmpi 0x100000,KERNEL_CS |
l1: |
.byte 0xEA,0x00,0x00,0x10,0x00,0x10,0x00 |
/smp.c
0,0 → 1,1233
/* |
* Intel MP v1.1/v1.4 specification support routines for multi-pentium |
* hosts. |
* |
* (c) 1995 Alan Cox, CymruNET Ltd <alan@cymru.net> |
* Supported by Caldera http://www.caldera.com. |
* Much of the core SMP work is based on previous work by Thomas Radke, to |
* whom a great many thanks are extended. |
* |
* Thanks to Intel for making available several different Pentium and |
* Pentium Pro MP machines. |
* |
* This code is released under the GNU public license version 2 or |
* later. |
* |
* Fixes |
* Felix Koop : NR_CPUS used properly |
* Jose Renau : Handle single CPU case. |
* Alan Cox : By repeated request 8) - Total BogoMIP report. |
* Greg Wright : Fix for kernel stacks panic. |
* Erich Boleyn : MP v1.4 and additional changes. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/string.h> |
#include <linux/timer.h> |
#include <linux/sched.h> |
#include <linux/mm.h> |
#include <linux/kernel_stat.h> |
#include <linux/delay.h> |
#include <linux/mc146818rtc.h> |
#include <asm/i82489.h> |
#include <linux/smp.h> |
#include <asm/pgtable.h> |
#include <asm/bitops.h> |
#include <asm/pgtable.h> |
#include <asm/smp.h> |
#ifdef CONFIG_MTRR |
#include <asm/mtrr.h> |
#endif |
|
/* |
* Why isn't this somewhere standard ?? |
*/ |
|
extern __inline int max(int a,int b) |
{ |
if(a>b) |
return a; |
return b; |
} |
|
|
int smp_found_config=0; /* Have we found an SMP box */ |
|
unsigned long cpu_present_map = 0; /* Bitmask of existing CPU's */ |
int smp_num_cpus = 1; /* Total count of live CPU's */ |
int smp_threads_ready=0; /* Set when the idlers are all forked */ |
volatile int cpu_number_map[NR_CPUS]; /* which CPU maps to which logical number */ |
volatile int cpu_logical_map[NR_CPUS]; /* which logical number maps to which CPU */ |
volatile unsigned long cpu_callin_map[NR_CPUS] = {0,}; /* We always use 0 the rest is ready for parallel delivery */ |
volatile unsigned long smp_invalidate_needed; /* Used for the invalidate map that's also checked in the spinlock */ |
struct cpuinfo_x86 cpu_data[NR_CPUS]; /* Per cpu bogomips and other parameters */ |
static unsigned int num_processors = 1; /* Internal processor count */ |
static unsigned long io_apic_addr = 0xFEC00000; /* Address of the I/O apic (not yet used) */ |
unsigned char boot_cpu_id = 0; /* Processor that is doing the boot up */ |
static unsigned char *kstack_base,*kstack_end; /* Kernel stack list pointers */ |
static int smp_activated = 0; /* Tripped once we need to start cross invalidating */ |
int apic_version[NR_CPUS]; /* APIC version number */ |
static volatile int smp_commenced=0; /* Tripped when we start scheduling */ |
unsigned long apic_addr=0xFEE00000; /* Address of APIC (defaults to 0xFEE00000) */ |
unsigned long nlong = 0; /* dummy used for apic_reg address + 0x20 */ |
unsigned char *apic_reg=((unsigned char *)(&nlong))-0x20;/* Later set to the vremap() of the APIC */ |
unsigned long apic_retval; /* Just debugging the assembler.. */ |
unsigned char *kernel_stacks[NR_CPUS]; /* Kernel stack pointers for CPU's (debugging) */ |
|
static volatile unsigned char smp_cpu_in_msg[NR_CPUS]; /* True if this processor is sending an IPI */ |
static volatile unsigned long smp_msg_data; /* IPI data pointer */ |
static volatile int smp_src_cpu; /* IPI sender processor */ |
static volatile int smp_msg_id; /* Message being sent */ |
|
volatile unsigned long kernel_flag=0; /* Kernel spinlock */ |
volatile unsigned char active_kernel_processor = NO_PROC_ID; /* Processor holding kernel spinlock */ |
volatile unsigned long kernel_counter=0; /* Number of times the processor holds the lock */ |
volatile unsigned long syscall_count=0; /* Number of times the processor holds the syscall lock */ |
|
volatile unsigned long ipi_count; /* Number of IPI's delivered */ |
#ifdef __SMP_PROF__ |
volatile unsigned long smp_spins[NR_CPUS]={0}; /* Count interrupt spins */ |
volatile unsigned long smp_spins_syscall[NR_CPUS]={0}; /* Count syscall spins */ |
volatile unsigned long smp_spins_syscall_cur[NR_CPUS]={0};/* Count spins for the actual syscall */ |
volatile unsigned long smp_spins_sys_idle[NR_CPUS]={0}; /* Count spins for sys_idle */ |
volatile unsigned long smp_idle_count[1+NR_CPUS]={0,}; /* Count idle ticks */ |
#endif |
#if defined (__SMP_PROF__) |
volatile unsigned long smp_idle_map=0; /* Map for idle processors */ |
#endif |
|
volatile unsigned long smp_proc_in_lock[NR_CPUS] = {0,};/* for computing process time */ |
volatile unsigned long smp_process_available=0; |
|
/*#define SMP_DEBUG*/ |
|
#ifdef SMP_DEBUG |
#define SMP_PRINTK(x) printk x |
#else |
#define SMP_PRINTK(x) |
#endif |
|
|
/* |
* Checksum an MP configuration block. |
*/ |
|
static int mpf_checksum(unsigned char *mp, int len) |
{ |
int sum=0; |
while(len--) |
sum+=*mp++; |
return sum&0xFF; |
} |
|
/* |
* Processor encoding in an MP configuration block |
*/ |
|
static char *mpc_family(int family,int model) |
{ |
static char n[32]; |
static char *model_defs[]= |
{ |
"80486DX","80486DX", |
"80486SX","80486DX/2 or 80487", |
"80486SL","Intel5X2(tm)", |
"Unknown","Unknown", |
"80486DX/4" |
}; |
if(family==0x6) |
return("Pentium(tm) Pro"); |
if(family==0x5) |
return("Pentium(tm)"); |
if(family==0x0F && model==0x0F) |
return("Special controller"); |
if(family==0x04 && model<9) |
return model_defs[model]; |
sprintf(n,"Unknown CPU [%d:%d]",family, model); |
return n; |
} |
|
/* |
* Read the MPC |
*/ |
|
static int smp_read_mpc(struct mp_config_table *mpc) |
{ |
char str[16]; |
int count=sizeof(*mpc); |
int apics=0; |
unsigned char *mpt=((unsigned char *)mpc)+count; |
|
if(memcmp(mpc->mpc_signature,MPC_SIGNATURE,4)) |
{ |
printk("Bad signature [%c%c%c%c].\n", |
mpc->mpc_signature[0], |
mpc->mpc_signature[1], |
mpc->mpc_signature[2], |
mpc->mpc_signature[3]); |
return 1; |
} |
if(mpf_checksum((unsigned char *)mpc,mpc->mpc_length)) |
{ |
printk("Checksum error.\n"); |
return 1; |
} |
if(mpc->mpc_spec!=0x01 && mpc->mpc_spec!=0x04) |
{ |
printk("Bad Config Table version (%d)!!\n",mpc->mpc_spec); |
return 1; |
} |
memcpy(str,mpc->mpc_oem,8); |
str[8]=0; |
printk("OEM ID: %s ",str); |
memcpy(str,mpc->mpc_productid,12); |
str[12]=0; |
printk("Product ID: %s ",str); |
printk("APIC at: 0x%lX\n",mpc->mpc_lapic); |
|
/* set the local APIC address */ |
apic_addr = mpc->mpc_lapic; |
|
/* |
* Now process the configuration blocks. |
*/ |
|
while(count<mpc->mpc_length) |
{ |
switch(*mpt) |
{ |
case MP_PROCESSOR: |
{ |
struct mpc_config_processor *m= |
(struct mpc_config_processor *)mpt; |
if(m->mpc_cpuflag&CPU_ENABLED) |
{ |
printk("Processor #%d %s APIC version %d\n", |
m->mpc_apicid, |
mpc_family((m->mpc_cpufeature& |
CPU_FAMILY_MASK)>>8, |
(m->mpc_cpufeature& |
CPU_MODEL_MASK)>>4), |
m->mpc_apicver); |
#ifdef SMP_DEBUG |
if(m->mpc_featureflag&(1<<0)) |
printk(" Floating point unit present.\n"); |
if(m->mpc_featureflag&(1<<7)) |
printk(" Machine Exception supported.\n"); |
if(m->mpc_featureflag&(1<<8)) |
printk(" 64 bit compare & exchange supported.\n"); |
if(m->mpc_featureflag&(1<<9)) |
printk(" Internal APIC present.\n"); |
#endif |
if(m->mpc_cpuflag&CPU_BOOTPROCESSOR) |
{ |
SMP_PRINTK((" Bootup CPU\n")); |
boot_cpu_id=m->mpc_apicid; |
} |
else /* Boot CPU already counted */ |
num_processors++; |
|
if(m->mpc_apicid>NR_CPUS) |
printk("Processor #%d unused. (Max %d processors).\n",m->mpc_apicid, NR_CPUS); |
else |
{ |
cpu_present_map|=(1<<m->mpc_apicid); |
apic_version[m->mpc_apicid]=m->mpc_apicver; |
} |
} |
mpt+=sizeof(*m); |
count+=sizeof(*m); |
break; |
} |
case MP_BUS: |
{ |
struct mpc_config_bus *m= |
(struct mpc_config_bus *)mpt; |
memcpy(str,m->mpc_bustype,6); |
str[6]=0; |
SMP_PRINTK(("Bus #%d is %s\n", |
m->mpc_busid, |
str)); |
mpt+=sizeof(*m); |
count+=sizeof(*m); |
break; |
} |
case MP_IOAPIC: |
{ |
struct mpc_config_ioapic *m= |
(struct mpc_config_ioapic *)mpt; |
if(m->mpc_flags&MPC_APIC_USABLE) |
{ |
apics++; |
printk("I/O APIC #%d Version %d at 0x%lX.\n", |
m->mpc_apicid,m->mpc_apicver, |
m->mpc_apicaddr); |
io_apic_addr = m->mpc_apicaddr; |
} |
mpt+=sizeof(*m); |
count+=sizeof(*m); |
break; |
} |
case MP_INTSRC: |
{ |
struct mpc_config_intsrc *m= |
(struct mpc_config_intsrc *)mpt; |
|
mpt+=sizeof(*m); |
count+=sizeof(*m); |
break; |
} |
case MP_LINTSRC: |
{ |
struct mpc_config_intlocal *m= |
(struct mpc_config_intlocal *)mpt; |
mpt+=sizeof(*m); |
count+=sizeof(*m); |
break; |
} |
} |
} |
if(apics>1) |
printk("Warning: Multiple APIC's not supported.\n"); |
return num_processors; |
} |
|
/* |
* Scan the memory blocks for an SMP configuration block. |
*/ |
|
int smp_scan_config(unsigned long base, unsigned long length) |
{ |
unsigned long *bp=(unsigned long *)base; |
struct intel_mp_floating *mpf; |
|
SMP_PRINTK(("Scan SMP from %p for %ld bytes.\n", |
bp,length)); |
if(sizeof(*mpf)!=16) |
printk("Error: MPF size\n"); |
|
while(length>0) |
{ |
if(*bp==SMP_MAGIC_IDENT) |
{ |
mpf=(struct intel_mp_floating *)bp; |
if(mpf->mpf_length==1 && |
!mpf_checksum((unsigned char *)bp,16) && |
(mpf->mpf_specification == 1 |
|| mpf->mpf_specification == 4) ) |
{ |
printk("Intel MultiProcessor Specification v1.%d\n", mpf->mpf_specification); |
if(mpf->mpf_feature2&(1<<7)) |
printk(" IMCR and PIC compatibility mode.\n"); |
else |
printk(" Virtual Wire compatibility mode.\n"); |
smp_found_config=1; |
/* |
* Now see if we need to read further. |
*/ |
if(mpf->mpf_feature1!=0) |
{ |
unsigned long cfg; |
|
/* |
* We need to know what the local |
* APIC id of the boot CPU is! |
*/ |
|
/* |
* |
* HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK |
* |
* It's not just a crazy hack... ;-) |
*/ |
/* |
* Standard page mapping |
* functions don't work yet. |
* We know that page 0 is not |
* used. Steal it for now! |
*/ |
|
cfg=pg0[0]; |
pg0[0] = (apic_addr | 7); |
local_flush_tlb(); |
|
boot_cpu_id = GET_APIC_ID(*((volatile unsigned long *) APIC_ID)); |
|
/* |
* Give it back |
*/ |
|
pg0[0]= cfg; |
local_flush_tlb(); |
|
/* |
* |
* END OF HACK END OF HACK END OF HACK END OF HACK END OF HACK |
* |
*/ |
/* |
* 2 CPUs, numbered 0 & 1. |
*/ |
cpu_present_map=3; |
num_processors=2; |
printk("I/O APIC at 0xFEC00000.\n"); |
printk("Bus#0 is "); |
} |
switch(mpf->mpf_feature1) |
{ |
case 1: |
case 5: |
printk("ISA\n"); |
break; |
case 2: |
printk("EISA with no IRQ8 chaining\n"); |
break; |
case 6: |
case 3: |
printk("EISA\n"); |
break; |
case 4: |
case 7: |
printk("MCA\n"); |
break; |
case 0: |
break; |
default: |
printk("???\nUnknown standard configuration %d\n", |
mpf->mpf_feature1); |
return 1; |
} |
if(mpf->mpf_feature1>4) |
{ |
printk("Bus #1 is PCI\n"); |
|
/* |
* Set local APIC version to |
* the integrated form. |
* It's initialized to zero |
* otherwise, representing |
* a discrete 82489DX. |
*/ |
apic_version[0] = 0x10; |
apic_version[1] = 0x10; |
} |
/* |
* Read the physical hardware table. |
* Anything here will override the |
* defaults. |
*/ |
if(mpf->mpf_physptr) |
smp_read_mpc((void *)mpf->mpf_physptr); |
|
/* |
* Now that the boot CPU id is known, |
* set some other information about it. |
*/ |
nlong = boot_cpu_id<<24; /* Dummy 'self' for bootup */ |
cpu_logical_map[0] = boot_cpu_id; |
|
printk("Processors: %d\n", num_processors); |
/* |
* Only use the first configuration found. |
*/ |
return 1; |
} |
} |
bp+=4; |
length-=16; |
} |
|
return 0; |
} |
|
/* |
* Trampoline 80x86 program as an array. |
*/ |
|
static unsigned char trampoline_data[]={ |
#include "trampoline.hex" |
}; |
|
/* |
* Currently trivial. Write the real->protected mode |
* bootstrap into the page concerned. The caller |
* has made sure it's suitably aligned. |
*/ |
|
static void install_trampoline(unsigned char *mp) |
{ |
memcpy(mp,trampoline_data,sizeof(trampoline_data)); |
} |
|
/* |
* We are called very early to get the low memory for the trampoline/kernel stacks |
* This has to be done by mm/init.c to parcel us out nice low memory. We allocate |
* the kernel stacks at 4K, 8K, 12K... currently (0-03FF is preserved for SMM and |
* other things). |
*/ |
|
unsigned long smp_alloc_memory(unsigned long mem_base) |
{ |
int size=(num_processors-1)*PAGE_SIZE; /* Number of stacks needed */ |
/* |
* Our stacks have to be below the 1Mb line, and mem_base on entry |
* is 4K aligned. |
*/ |
|
if(mem_base+size>=0x9F000) |
panic("smp_alloc_memory: Insufficient low memory for kernel stacks.\n"); |
kstack_base=(void *)mem_base; |
mem_base+=size; |
kstack_end=(void *)mem_base; |
return mem_base; |
} |
|
/* |
* Hand out stacks one at a time. |
*/ |
|
static void *get_kernel_stack(void) |
{ |
void *stack=kstack_base; |
if(kstack_base>=kstack_end) |
return NULL; |
kstack_base+=PAGE_SIZE; |
return stack; |
} |
|
|
/* |
* The bootstrap kernel entry code has set these up. Save them for |
* a given CPU |
*/ |
|
void smp_store_cpu_info(int id) |
{ |
struct cpuinfo_x86 *c=&cpu_data[id]; |
c->hard_math=hard_math; /* Always assumed same currently */ |
c->x86=x86; |
c->x86_model=x86_model; |
c->x86_mask=x86_mask; |
c->x86_capability=x86_capability; |
c->fdiv_bug=fdiv_bug; |
c->wp_works_ok=wp_works_ok; /* Always assumed the same currently */ |
c->hlt_works_ok=hlt_works_ok; |
c->have_cpuid=have_cpuid; |
c->udelay_val=loops_per_sec; |
strcpy(c->x86_vendor_id, x86_vendor_id); |
} |
|
/* |
* Architecture specific routine called by the kernel just before init is |
* fired off. This allows the BP to have everything in order [we hope]. |
* At the end of this all the AP's will hit the system scheduling and off |
* we go. Each AP will load the system gdt's and jump through the kernel |
* init into idle(). At this point the scheduler will one day take over |
* and give them jobs to do. smp_callin is a standard routine |
* we use to track CPU's as they power up. |
*/ |
|
void smp_commence(void) |
{ |
/* |
* Lets the callin's below out of their loop. |
*/ |
smp_commenced=1; |
} |
|
void smp_callin(void) |
{ |
extern void calibrate_delay(void); |
int cpuid=GET_APIC_ID(apic_read(APIC_ID)); |
unsigned long l; |
extern struct desc_struct idt_descriptor; |
extern int pentium_f00f_bug; |
|
if (pentium_f00f_bug) { |
__asm__ __volatile__("\tlidt %0": "=m" (idt_descriptor)); |
} |
|
/* |
* Activate our APIC |
*/ |
|
SMP_PRINTK(("CALLIN %d\n",smp_processor_id())); |
l=apic_read(APIC_SPIV); |
l|=(1<<8); /* Enable */ |
apic_write(APIC_SPIV,l); |
|
#ifdef CONFIG_MTRR |
/* |
* checks the MTRR configuration of this application processor |
*/ |
check_mtrr_config(); |
#endif |
|
sti(); |
/* |
* Get our bogomips. |
*/ |
calibrate_delay(); |
/* |
* Save our processor parameters |
*/ |
smp_store_cpu_info(cpuid); |
/* |
* Allow the master to continue. |
*/ |
set_bit(cpuid, (unsigned long *)&cpu_callin_map[0]); |
/* |
* Until we are ready for SMP scheduling |
*/ |
load_ldt(0); |
/* printk("Testing faulting...\n"); |
*(long *)0=1; OOPS... */ |
local_flush_tlb(); |
while(!smp_commenced); |
if (cpu_number_map[cpuid] == -1) |
while(1); |
local_flush_tlb(); |
SMP_PRINTK(("Commenced..\n")); |
|
load_TR(cpu_number_map[cpuid]); |
/* while(1);*/ |
} |
|
/* |
* Cycle through the processors sending pentium IPI's to boot each. |
*/ |
|
void smp_boot_cpus(void) |
{ |
int i; |
int cpucount=0; |
unsigned long cfg; |
void *stack; |
extern unsigned long init_user_stack[]; |
|
/* |
* Initialize the logical to physical cpu number mapping |
*/ |
|
for (i = 0; i < NR_CPUS; i++) |
cpu_number_map[i] = -1; |
|
/* |
* Setup boot CPU information |
*/ |
|
kernel_stacks[boot_cpu_id]=(void *)init_user_stack; /* Set up for boot processor first */ |
|
smp_store_cpu_info(boot_cpu_id); /* Final full version of the data */ |
|
cpu_present_map |= (1 << smp_processor_id()); |
cpu_number_map[boot_cpu_id] = 0; |
active_kernel_processor=boot_cpu_id; |
|
/* |
* If we don't conform to the Intel MPS standard, get out |
* of here now! |
*/ |
|
if (!smp_found_config) |
return; |
|
/* |
* Map the local APIC into kernel space |
*/ |
|
apic_reg = vremap(apic_addr,4096); |
|
if(apic_reg == NULL) |
panic("Unable to map local apic.\n"); |
|
#ifdef SMP_DEBUG |
{ |
int reg; |
|
/* |
* This is to verify that we're looking at |
* a real local APIC. Check these against |
* your board if the CPUs aren't getting |
* started for no apparent reason. |
*/ |
|
reg = apic_read(APIC_VERSION); |
SMP_PRINTK(("Getting VERSION: %x\n", reg)); |
|
apic_write(APIC_VERSION, 0); |
reg = apic_read(APIC_VERSION); |
SMP_PRINTK(("Getting VERSION: %x\n", reg)); |
|
/* |
* The two version reads above should print the same |
* NON-ZERO!!! numbers. If the second one is zero, |
* there is a problem with the APIC write/read |
* definitions. |
* |
* The next two are just to see if we have sane values. |
* They're only really relevant if we're in Virtual Wire |
* compatibility mode, but most boxes are anymore. |
*/ |
|
|
reg = apic_read(APIC_LVT0); |
SMP_PRINTK(("Getting LVT0: %x\n", reg)); |
|
reg = apic_read(APIC_LVT1); |
SMP_PRINTK(("Getting LVT1: %x\n", reg)); |
} |
#endif |
|
/* |
* Enable the local APIC |
*/ |
|
cfg=apic_read(APIC_SPIV); |
cfg|=(1<<8); /* Enable APIC */ |
apic_write(APIC_SPIV,cfg); |
|
udelay(10); |
|
/* |
* Now scan the cpu present map and fire up the other CPUs. |
*/ |
|
SMP_PRINTK(("CPU map: %lx\n", cpu_present_map)); |
|
for(i=0;i<NR_CPUS;i++) |
{ |
/* |
* Don't even attempt to start the boot CPU! |
*/ |
if (i == boot_cpu_id) |
continue; |
|
if (cpu_present_map & (1 << i)) |
{ |
unsigned long send_status, accept_status; |
int timeout, num_starts, j; |
|
/* |
* We need a kernel stack for each processor. |
*/ |
|
stack=get_kernel_stack(); /* We allocated these earlier */ |
if(stack==NULL) |
panic("No memory for processor stacks.\n"); |
kernel_stacks[i]=stack; |
install_trampoline(stack); |
|
printk("Booting processor %d stack %p: ",i,stack); /* So we set what's up */ |
|
/* |
* This grunge runs the startup process for |
* the targeted processor. |
*/ |
|
SMP_PRINTK(("Setting warm reset code and vector.\n")); |
|
/* |
* Install a writable page 0 entry. |
*/ |
|
cfg=pg0[0]; |
|
CMOS_WRITE(0xa, 0xf); |
pg0[0]=7; |
local_flush_tlb(); |
*((volatile unsigned short *) 0x469) = ((unsigned long)stack)>>4; |
*((volatile unsigned short *) 0x467) = 0; |
|
/* |
* Protect it again |
*/ |
|
pg0[0]= cfg; |
local_flush_tlb(); |
|
/* |
* Be paranoid about clearing APIC errors. |
*/ |
|
if ( apic_version[i] & 0xF0 ) |
{ |
apic_write(APIC_ESR, 0); |
accept_status = (apic_read(APIC_ESR) & 0xEF); |
} |
|
/* |
* Status is now clean |
*/ |
|
send_status = 0; |
accept_status = 0; |
|
/* |
* Starting actual IPI sequence... |
*/ |
|
SMP_PRINTK(("Asserting INIT.\n")); |
|
/* |
* Turn INIT on |
*/ |
|
cfg=apic_read(APIC_ICR2); |
cfg&=0x00FFFFFF; |
apic_write(APIC_ICR2, cfg|SET_APIC_DEST_FIELD(i)); /* Target chip */ |
cfg=apic_read(APIC_ICR); |
cfg&=~0xCDFFF; /* Clear bits */ |
cfg |= (APIC_DEST_FIELD | APIC_DEST_LEVELTRIG |
| APIC_DEST_ASSERT | APIC_DEST_DM_INIT); |
apic_write(APIC_ICR, cfg); /* Send IPI */ |
|
udelay(200); |
SMP_PRINTK(("Deasserting INIT.\n")); |
|
cfg=apic_read(APIC_ICR2); |
cfg&=0x00FFFFFF; |
apic_write(APIC_ICR2, cfg|SET_APIC_DEST_FIELD(i)); /* Target chip */ |
cfg=apic_read(APIC_ICR); |
cfg&=~0xCDFFF; /* Clear bits */ |
cfg |= (APIC_DEST_FIELD | APIC_DEST_LEVELTRIG |
| APIC_DEST_DM_INIT); |
apic_write(APIC_ICR, cfg); /* Send IPI */ |
|
/* |
* Should we send STARTUP IPIs ? |
* |
* Determine this based on the APIC version. |
* If we don't have an integrated APIC, don't |
* send the STARTUP IPIs. |
*/ |
|
if ( apic_version[i] & 0xF0 ) |
num_starts = 2; |
else |
num_starts = 0; |
|
/* |
* Run STARTUP IPI loop. |
*/ |
|
for (j = 1; !(send_status || accept_status) |
&& (j <= num_starts) ; j++) |
{ |
SMP_PRINTK(("Sending STARTUP #%d.\n",j)); |
|
apic_write(APIC_ESR, 0); |
|
/* |
* STARTUP IPI |
*/ |
|
cfg=apic_read(APIC_ICR2); |
cfg&=0x00FFFFFF; |
apic_write(APIC_ICR2, cfg|SET_APIC_DEST_FIELD(i)); /* Target chip */ |
cfg=apic_read(APIC_ICR); |
cfg&=~0xCDFFF; /* Clear bits */ |
cfg |= (APIC_DEST_FIELD |
| APIC_DEST_DM_STARTUP |
| (((int) stack) >> 12) ); /* Boot on the stack */ |
apic_write(APIC_ICR, cfg); /* Kick the second */ |
|
timeout = 0; |
do { |
udelay(10); |
} while ( (send_status = (apic_read(APIC_ICR) & 0x1000)) |
&& (timeout++ < 1000)); |
udelay(200); |
|
accept_status = (apic_read(APIC_ESR) & 0xEF); |
} |
|
if (send_status) /* APIC never delivered?? */ |
printk("APIC never delivered???\n"); |
if (accept_status) /* Send accept error */ |
printk("APIC delivery error (%lx).\n", accept_status); |
|
if( !(send_status || accept_status) ) |
{ |
for(timeout=0;timeout<50000;timeout++) |
{ |
if(cpu_callin_map[0]&(1<<i)) |
break; /* It has booted */ |
udelay(100); /* Wait 5s total for a response */ |
} |
if(cpu_callin_map[0]&(1<<i)) |
{ |
cpucount++; |
/* number CPUs logically, starting from 1 (BSP is 0) */ |
cpu_number_map[i] = cpucount; |
cpu_logical_map[cpucount] = i; |
} |
else |
{ |
if(*((volatile unsigned char *)8192)==0xA5) |
printk("Stuck ??\n"); |
else |
printk("Not responding.\n"); |
} |
} |
|
/* mark "stuck" area as not stuck */ |
*((volatile unsigned long *)8192) = 0; |
} |
|
/* |
* Make sure we unmap all failed CPUs |
*/ |
|
if (cpu_number_map[i] == -1) |
cpu_present_map &= ~(1 << i); |
} |
|
/* |
* Cleanup possible dangling ends... |
*/ |
|
/* |
* Install writable page 0 entry. |
*/ |
|
cfg = pg0[0]; |
pg0[0] = 3; /* writeable, present, addr 0 */ |
local_flush_tlb(); |
|
/* |
* Paranoid: Set warm reset code and vector here back |
* to default values. |
*/ |
|
CMOS_WRITE(0, 0xf); |
|
*((volatile long *) 0x467) = 0; |
|
/* |
* Restore old page 0 entry. |
*/ |
|
pg0[0] = cfg; |
local_flush_tlb(); |
|
/* |
* Allow the user to impress friends. |
*/ |
|
if(cpucount==0) |
{ |
printk("Error: only one processor found.\n"); |
cpu_present_map=(1<<smp_processor_id()); |
} |
else |
{ |
unsigned long bogosum=0; |
for(i=0;i<32;i++) |
{ |
if(cpu_present_map&(1<<i)) |
bogosum+=cpu_data[i].udelay_val; |
} |
printk("Total of %d processors activated (%lu.%02lu BogoMIPS).\n", |
cpucount+1, |
(bogosum+2500)/500000, |
((bogosum+2500)/5000)%100); |
smp_activated=1; |
smp_num_cpus=cpucount+1; |
} |
} |
|
|
/* |
* A non wait message cannot pass data or cpu source info. This current setup |
* is only safe because the kernel lock owner is the only person who can send a message. |
* |
* Wrapping this whole block in a spinlock is not the safe answer either. A processor may |
* get stuck with irq's off waiting to send a message and thus not replying to the person |
* spinning for a reply.... |
* |
* In the end flush tlb ought to be the NMI and a very very short function (to avoid the old |
* IDE disk problems), and other messages sent with IRQ's enabled in a civilised fashion. That |
* will also boost performance. |
*/ |
|
void smp_message_pass(int target, int msg, unsigned long data, int wait) |
{ |
unsigned long cfg; |
unsigned long target_map; |
int p=smp_processor_id(); |
int irq=0x2d; /* IRQ 13 */ |
int ct=0; |
static volatile int message_cpu = NO_PROC_ID; |
|
/* |
* During boot up send no messages |
*/ |
|
if(!smp_activated || !smp_commenced) |
return; |
|
|
/* |
* Skip the reschedule if we are waiting to clear a |
* message at this time. The reschedule cannot wait |
* but is not critical. |
*/ |
|
if(msg==MSG_RESCHEDULE) /* Reschedules we do via trap 0x30 */ |
{ |
irq=0x30; |
if(smp_cpu_in_msg[p]) |
return; |
} |
|
/* |
* Sanity check we don't re-enter this across CPU's. Only the kernel |
* lock holder may send messages. For a STOP_CPU we are bringing the |
* entire box to the fastest halt we can.. A reschedule carries |
* no data and can occur during a flush.. guess what panic |
* I got to notice this bug... |
*/ |
|
if(message_cpu!=NO_PROC_ID && msg!=MSG_STOP_CPU && msg!=MSG_RESCHEDULE) |
{ |
panic("CPU #%d: Message pass %d but pass in progress by %d of %d\n", |
smp_processor_id(),msg,message_cpu, smp_msg_id); |
} |
message_cpu=smp_processor_id(); |
|
|
/* |
* We are busy |
*/ |
|
smp_cpu_in_msg[p]++; |
|
/* |
* Reschedule is currently special |
*/ |
|
if(msg!=MSG_RESCHEDULE) |
{ |
smp_src_cpu=p; |
smp_msg_id=msg; |
smp_msg_data=data; |
} |
|
/* printk("SMP message pass #%d to %d of %d\n", |
p, msg, target);*/ |
|
/* |
* Wait for the APIC to become ready - this should never occur. Its |
* a debugging check really. |
*/ |
|
while(ct<1000) |
{ |
cfg=apic_read(APIC_ICR); |
if(!(cfg&(1<<12))) |
break; |
ct++; |
udelay(10); |
} |
|
/* |
* Just pray... there is nothing more we can do |
*/ |
|
if(ct==1000) |
printk("CPU #%d: previous IPI still not cleared after 10ms\n", smp_processor_id()); |
|
/* |
* Program the APIC to deliver the IPI |
*/ |
|
cfg=apic_read(APIC_ICR2); |
cfg&=0x00FFFFFF; |
apic_write(APIC_ICR2, cfg|SET_APIC_DEST_FIELD(target)); /* Target chip */ |
cfg=apic_read(APIC_ICR); |
cfg&=~0xFDFFF; /* Clear bits */ |
cfg|=APIC_DEST_FIELD|APIC_DEST_DM_FIXED|irq; /* Send an IRQ 13 */ |
|
/* |
* Set the target requirement |
*/ |
|
if(target==MSG_ALL_BUT_SELF) |
{ |
cfg|=APIC_DEST_ALLBUT; |
target_map=cpu_present_map; |
cpu_callin_map[0]=(1<<smp_src_cpu); |
} |
else if(target==MSG_ALL) |
{ |
cfg|=APIC_DEST_ALLINC; |
target_map=cpu_present_map; |
cpu_callin_map[0]=0; |
} |
else |
{ |
target_map=(1<<target); |
cpu_callin_map[0]=0; |
} |
|
/* |
* Send the IPI. The write to APIC_ICR fires this off. |
*/ |
|
apic_write(APIC_ICR, cfg); |
|
/* |
* Spin waiting for completion |
*/ |
|
switch(wait) |
{ |
case 1: |
while(cpu_callin_map[0]!=target_map); /* Spin on the pass */ |
break; |
case 2: |
while(smp_invalidate_needed); /* Wait for invalidate map to clear */ |
break; |
} |
|
/* |
* Record our completion |
*/ |
|
smp_cpu_in_msg[p]--; |
message_cpu=NO_PROC_ID; |
} |
|
/* |
* This is fraught with deadlocks. Linus does a flush tlb at a whim |
* even with IRQ's off. We have to avoid a pair of crossing flushes |
* or we are doomed. See the notes about smp_message_pass. |
*/ |
|
void smp_flush_tlb(void) |
{ |
unsigned long flags; |
if(smp_activated && smp_processor_id()!=active_kernel_processor) |
panic("CPU #%d:Attempted flush tlb IPI when not AKP(=%d)\n",smp_processor_id(),active_kernel_processor); |
/* printk("SMI-");*/ |
|
/* |
* The assignment is safe because it's volatile so the compiler cannot reorder it, |
* because the i586 has strict memory ordering and because only the kernel lock holder |
* may issue a tlb flush. If you break any one of those three change this to an atomic |
* bus locked or. |
*/ |
|
smp_invalidate_needed=cpu_present_map&~(1<<smp_processor_id()); |
|
/* |
* Processors spinning on the lock will see this IRQ late. The smp_invalidate_needed map will |
* ensure they don't do a spurious flush tlb or miss one. |
*/ |
|
save_flags(flags); |
cli(); |
smp_message_pass(MSG_ALL_BUT_SELF, MSG_INVALIDATE_TLB, 0L, 2); |
|
/* |
* Flush the local TLB |
*/ |
|
local_flush_tlb(); |
|
restore_flags(flags); |
|
/* |
* Completed. |
*/ |
|
/* printk("SMID\n");*/ |
} |
|
/* |
* Reschedule call back |
*/ |
|
void smp_reschedule_irq(int cpl, struct pt_regs *regs) |
{ |
#ifdef DEBUGGING_SMP_RESCHED |
static int ct=0; |
if(ct==0) |
{ |
printk("Beginning scheduling on CPU#%d\n",smp_processor_id()); |
ct=1; |
} |
#endif |
if(smp_processor_id()!=active_kernel_processor) |
panic("SMP Reschedule on CPU #%d, but #%d is active.\n", |
smp_processor_id(), active_kernel_processor); |
|
need_resched=1; |
|
/* |
* Clear the IPI |
*/ |
apic_read(APIC_SPIV); /* Dummy read */ |
apic_write(APIC_EOI, 0); /* Docs say use 0 for future compatibility */ |
} |
|
/* |
* Message call back. |
*/ |
|
void smp_message_irq(int cpl, void *dev_id, struct pt_regs *regs) |
{ |
int i=smp_processor_id(); |
/* static int n=0; |
if(n++<NR_CPUS) |
printk("IPI %d->%d(%d,%ld)\n",smp_src_cpu,i,smp_msg_id,smp_msg_data);*/ |
switch(smp_msg_id) |
{ |
case 0: /* IRQ 13 testing - boring */ |
return; |
|
/* |
* A TLB flush is needed. |
*/ |
|
case MSG_INVALIDATE_TLB: |
if(clear_bit(i,(unsigned long *)&smp_invalidate_needed)) |
local_flush_tlb(); |
set_bit(i, (unsigned long *)&cpu_callin_map[0]); |
/* cpu_callin_map[0]|=1<<smp_processor_id();*/ |
break; |
|
/* |
* Halt other CPU's for a panic or reboot |
*/ |
case MSG_STOP_CPU: |
while(1) |
{ |
if(cpu_data[smp_processor_id()].hlt_works_ok) |
__asm__("hlt"); |
} |
default: |
printk("CPU #%d sent invalid cross CPU message to CPU #%d: %X(%lX).\n", |
smp_src_cpu,smp_processor_id(),smp_msg_id,smp_msg_data); |
break; |
} |
/* |
* Clear the IPI, so we can receive future IPI's |
*/ |
|
apic_read(APIC_SPIV); /* Dummy read */ |
apic_write(APIC_EOI, 0); /* Docs say use 0 for future compatibility */ |
} |
|
void irq_deadlock_detected(void) |
{ |
printk("IRQ DEADLOCK DETECTED BY CPU %d\n", smp_processor_id()); |
__asm__("hlt"); |
} |
|
void non_irq_deadlock_detected(void) |
{ |
printk("NON-IRQ DEADLOCK DETECTED BY CPU %d\n", smp_processor_id()); |
__asm__("hlt"); |
} |
/vm86.c
0,0 → 1,635
/* |
* linux/kernel/vm86.c |
* |
* Copyright (C) 1994 Linus Torvalds |
*/ |
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/signal.h> |
#include <linux/string.h> |
#include <linux/ptrace.h> |
#include <linux/mm.h> |
|
#include <asm/segment.h> |
#include <asm/pgtable.h> |
#include <asm/io.h> |
|
/* |
* Known problems: |
* |
* Interrupt handling is not guaranteed: |
* - a real x86 will disable all interrupts for one instruction |
* after a "mov ss,xx" to make stack handling atomic even without |
* the 'lss' instruction. We can't guarantee this in v86 mode, |
* as the next instruction might result in a page fault or similar. |
* - a real x86 will have interrupts disabled for one instruction |
* past the 'sti' that enables them. We don't bother with all the |
* details yet.. |
* |
* Hopefully these problems do not actually matter for anything. |
*/ |
|
|
#define KVM86 ((struct kernel_vm86_struct *)regs) |
#define VMPI KVM86->vm86plus |
|
|
/* |
* 8- and 16-bit register defines.. |
*/ |
#define AL(regs) (((unsigned char *)&((regs)->eax))[0]) |
#define AH(regs) (((unsigned char *)&((regs)->eax))[1]) |
#define IP(regs) (*(unsigned short *)&((regs)->eip)) |
#define SP(regs) (*(unsigned short *)&((regs)->esp)) |
|
/* |
* virtual flags (16 and 32-bit versions) |
*/ |
#define VFLAGS (*(unsigned short *)&(current->tss.v86flags)) |
#define VEFLAGS (current->tss.v86flags) |
|
#define set_flags(X,new,mask) \ |
((X) = ((X) & ~(mask)) | ((new) & (mask))) |
|
#define SAFE_MASK (0xDD5) |
#define RETURN_MASK (0xDFF) |
|
|
asmlinkage struct pt_regs * save_v86_state(struct vm86_regs * regs) |
{ |
if (!current->tss.vm86_info) { |
printk("no vm86_info: BAD\n"); |
do_exit(SIGSEGV); |
} |
set_flags(regs->eflags, VEFLAGS, VIF_MASK | current->tss.v86mask); |
memcpy_tofs(¤t->tss.vm86_info->regs,regs,sizeof(*regs)); |
put_fs_long(current->tss.screen_bitmap,¤t->tss.vm86_info->screen_bitmap); |
current->tss.esp0 = current->saved_kernel_stack; |
current->saved_kernel_stack = 0; |
return KVM86->regs32; |
} |
|
static void mark_screen_rdonly(struct task_struct * tsk) |
{ |
pgd_t *pgd; |
pmd_t *pmd; |
pte_t *pte; |
int i; |
|
pgd = pgd_offset(tsk->mm, 0xA0000); |
if (pgd_none(*pgd)) |
return; |
if (pgd_bad(*pgd)) { |
printk("vm86: bad pgd entry [%p]:%08lx\n", pgd, pgd_val(*pgd)); |
pgd_clear(pgd); |
return; |
} |
pmd = pmd_offset(pgd, 0xA0000); |
if (pmd_none(*pmd)) |
return; |
if (pmd_bad(*pmd)) { |
printk("vm86: bad pmd entry [%p]:%08lx\n", pmd, pmd_val(*pmd)); |
pmd_clear(pmd); |
return; |
} |
pte = pte_offset(pmd, 0xA0000); |
for (i = 0; i < 32; i++) { |
if (pte_present(*pte)) |
set_pte(pte, pte_wrprotect(*pte)); |
pte++; |
} |
flush_tlb(); |
} |
|
|
|
static int do_vm86_irq_handling(int subfunction, int irqnumber); |
static void do_sys_vm86(struct kernel_vm86_struct *info, struct task_struct *tsk); |
|
asmlinkage int sys_vm86old(struct vm86_struct * v86) |
{ |
struct kernel_vm86_struct info; /* declare this _on top_, |
* this avoids wasting of stack space. |
* This remains on the stack until we |
* return to 32 bit user space. |
*/ |
struct task_struct *tsk = current; |
int error; |
|
if (tsk->saved_kernel_stack) |
return -EPERM; |
/* v86 must be readable (now) and writable (for save_v86_state) */ |
error = verify_area(VERIFY_WRITE,v86,sizeof(*v86)); |
if (error) |
return error; |
memcpy_fromfs(&info,v86,sizeof(struct vm86_struct)); |
memset(&info.vm86plus, 0, (int)&info.regs32 - (int)&info.vm86plus); |
info.regs32 = (struct pt_regs *) &v86; |
tsk->tss.vm86_info = v86; |
do_sys_vm86(&info, tsk); |
return 0; /* we never return here */ |
} |
|
|
asmlinkage int sys_vm86(unsigned long subfunction, struct vm86plus_struct * v86) |
{ |
struct kernel_vm86_struct info; /* declare this _on top_, |
* this avoids wasting of stack space. |
* This remains on the stack until we |
* return to 32 bit user space. |
*/ |
struct task_struct *tsk = current; |
int error; |
|
switch (subfunction) { |
case VM86_REQUEST_IRQ: |
case VM86_FREE_IRQ: |
case VM86_GET_IRQ_BITS: |
case VM86_GET_AND_RESET_IRQ: |
return do_vm86_irq_handling(subfunction,(int)v86); |
case VM86_PLUS_INSTALL_CHECK: |
/* NOTE: on old vm86 stuff this will return the error |
from verify_area(), because the subfunction is |
interpreted as (invalid) address to vm86_struct. |
So the installation check works. |
*/ |
return 0; |
} |
|
/* we come here only for functions VM86_ENTER, VM86_ENTER_NO_BYPASS */ |
if (tsk->saved_kernel_stack) |
return -EPERM; |
/* v86 must be readable (now) and writable (for save_v86_state) */ |
error = verify_area(VERIFY_WRITE,v86,sizeof(struct vm86plus_struct)); |
if (error) |
return error; |
memcpy_fromfs(&info,v86,sizeof(struct vm86plus_struct)); |
info.regs32 = (struct pt_regs *) &subfunction; |
info.vm86plus.is_vm86pus = 1; |
tsk->tss.vm86_info = (struct vm86_struct *)v86; |
do_sys_vm86(&info, tsk); |
return 0; /* we never return here */ |
} |
|
|
static void do_sys_vm86(struct kernel_vm86_struct *info, struct task_struct *tsk) |
{ |
/* |
* make sure the vm86() system call doesn't try to do anything silly |
*/ |
info->regs.__null_ds = 0; |
info->regs.__null_es = 0; |
info->regs.__null_fs = 0; |
info->regs.__null_gs = 0; |
/* |
* The eflags register is also special: we cannot trust that the user |
* has set it up safely, so this makes sure interrupt etc flags are |
* inherited from protected mode. |
*/ |
VEFLAGS = info->regs.eflags; |
info->regs.eflags &= SAFE_MASK; |
info->regs.eflags |= info->regs32->eflags & ~SAFE_MASK; |
info->regs.eflags |= VM_MASK; |
|
switch (info->cpu_type) { |
case CPU_286: |
tsk->tss.v86mask = 0; |
break; |
case CPU_386: |
tsk->tss.v86mask = NT_MASK | IOPL_MASK; |
break; |
case CPU_486: |
tsk->tss.v86mask = AC_MASK | NT_MASK | IOPL_MASK; |
break; |
default: |
tsk->tss.v86mask = ID_MASK | AC_MASK | NT_MASK | IOPL_MASK; |
break; |
} |
|
/* |
* Save old state, set default return value (%eax) to 0 |
*/ |
info->regs32->eax = 0; |
tsk->saved_kernel_stack = tsk->tss.esp0; |
tsk->tss.esp0 = (unsigned long) &info->VM86_TSS_ESP0; |
|
tsk->tss.screen_bitmap = info->screen_bitmap; |
if (info->flags & VM86_SCREEN_BITMAP) |
mark_screen_rdonly(tsk); |
__asm__ __volatile__( |
"movl %0,%%esp\n\t" |
"jmp ret_from_sys_call" |
: /* no outputs */ |
:"r" (&info->regs)); |
/* we never return here */ |
} |
|
static inline void return_to_32bit(struct vm86_regs * regs16, int retval) |
{ |
struct pt_regs * regs32; |
|
regs32 = save_v86_state(regs16); |
regs32->eax = retval; |
__asm__ __volatile__("movl %0,%%esp\n\t" |
"jmp ret_from_sys_call" |
: : "r" (regs32)); |
} |
|
static inline void set_IF(struct vm86_regs * regs) |
{ |
VEFLAGS |= VIF_MASK; |
if (VEFLAGS & VIP_MASK) |
return_to_32bit(regs, VM86_STI); |
} |
|
static inline void clear_IF(struct vm86_regs * regs) |
{ |
VEFLAGS &= ~VIF_MASK; |
} |
|
static inline void clear_TF(struct vm86_regs * regs) |
{ |
regs->eflags &= ~TF_MASK; |
} |
|
static inline void set_vflags_long(unsigned long eflags, struct vm86_regs * regs) |
{ |
set_flags(VEFLAGS, eflags, current->tss.v86mask); |
set_flags(regs->eflags, eflags, SAFE_MASK); |
if (eflags & IF_MASK) |
set_IF(regs); |
} |
|
static inline void set_vflags_short(unsigned short flags, struct vm86_regs * regs) |
{ |
set_flags(VFLAGS, flags, current->tss.v86mask); |
set_flags(regs->eflags, flags, SAFE_MASK); |
if (flags & IF_MASK) |
set_IF(regs); |
} |
|
static inline unsigned long get_vflags(struct vm86_regs * regs) |
{ |
unsigned long flags = regs->eflags & RETURN_MASK; |
|
if (VEFLAGS & VIF_MASK) |
flags |= IF_MASK; |
return flags | (VEFLAGS & current->tss.v86mask); |
} |
|
static inline int is_revectored(int nr, struct revectored_struct * bitmap) |
{ |
__asm__ __volatile__("btl %2,%1\n\tsbbl %0,%0" |
:"=r" (nr) |
:"m" (*bitmap),"r" (nr)); |
return nr; |
} |
|
/* |
* Boy are these ugly, but we need to do the correct 16-bit arithmetic. |
* Gcc makes a mess of it, so we do it inline and use non-obvious calling |
* conventions.. |
*/ |
#define pushb(base, ptr, val) \ |
__asm__ __volatile__( \ |
"decw %w0\n\t" \ |
"movb %2,%%fs:0(%1,%0)" \ |
: "=r" (ptr) \ |
: "r" (base), "q" (val), "0" (ptr)) |
|
#define pushw(base, ptr, val) \ |
__asm__ __volatile__( \ |
"decw %w0\n\t" \ |
"movb %h2,%%fs:0(%1,%0)\n\t" \ |
"decw %w0\n\t" \ |
"movb %b2,%%fs:0(%1,%0)" \ |
: "=r" (ptr) \ |
: "r" (base), "q" (val), "0" (ptr)) |
|
#define pushl(base, ptr, val) \ |
__asm__ __volatile__( \ |
"decw %w0\n\t" \ |
"rorl $16,%2\n\t" \ |
"movb %h2,%%fs:0(%1,%0)\n\t" \ |
"decw %w0\n\t" \ |
"movb %b2,%%fs:0(%1,%0)\n\t" \ |
"decw %w0\n\t" \ |
"rorl $16,%2\n\t" \ |
"movb %h2,%%fs:0(%1,%0)\n\t" \ |
"decw %w0\n\t" \ |
"movb %b2,%%fs:0(%1,%0)" \ |
: "=r" (ptr) \ |
: "r" (base), "q" (val), "0" (ptr)) |
|
#define popb(base, ptr) \ |
({ unsigned long __res; \ |
__asm__ __volatile__( \ |
"movb %%fs:0(%1,%0),%b2\n\t" \ |
"incw %w0" \ |
: "=r" (ptr), "=r" (base), "=q" (__res) \ |
: "0" (ptr), "1" (base), "2" (0)); \ |
__res; }) |
|
#define popw(base, ptr) \ |
({ unsigned long __res; \ |
__asm__ __volatile__( \ |
"movb %%fs:0(%1,%0),%b2\n\t" \ |
"incw %w0\n\t" \ |
"movb %%fs:0(%1,%0),%h2\n\t" \ |
"incw %w0" \ |
: "=r" (ptr), "=r" (base), "=q" (__res) \ |
: "0" (ptr), "1" (base), "2" (0)); \ |
__res; }) |
|
#define popl(base, ptr) \ |
({ unsigned long __res; \ |
__asm__ __volatile__( \ |
"movb %%fs:0(%1,%0),%b2\n\t" \ |
"incw %w0\n\t" \ |
"movb %%fs:0(%1,%0),%h2\n\t" \ |
"incw %w0\n\t" \ |
"rorl $16,%2\n\t" \ |
"movb %%fs:0(%1,%0),%b2\n\t" \ |
"incw %w0\n\t" \ |
"movb %%fs:0(%1,%0),%h2\n\t" \ |
"incw %w0\n\t" \ |
"rorl $16,%2" \ |
: "=r" (ptr), "=r" (base), "=q" (__res) \ |
: "0" (ptr), "1" (base)); \ |
__res; }) |
|
static void do_int(struct vm86_regs *regs, int i, unsigned char * ssp, unsigned long sp) |
{ |
unsigned long *intr_ptr, segoffs; |
|
if (regs->cs == BIOSSEG) |
goto cannot_handle; |
if (is_revectored(i, &KVM86->int_revectored)) |
goto cannot_handle; |
if (i==0x21 && is_revectored(AH(regs),&KVM86->int21_revectored)) |
goto cannot_handle; |
intr_ptr = (unsigned long *) (i << 2); |
if (verify_area(VERIFY_READ, intr_ptr, 4) < 0) |
goto cannot_handle; |
segoffs = get_fs_long(intr_ptr); |
if ((segoffs >> 16) == BIOSSEG) |
goto cannot_handle; |
pushw(ssp, sp, get_vflags(regs)); |
pushw(ssp, sp, regs->cs); |
pushw(ssp, sp, IP(regs)); |
regs->cs = segoffs >> 16; |
SP(regs) -= 6; |
IP(regs) = segoffs & 0xffff; |
clear_TF(regs); |
clear_IF(regs); |
return; |
|
cannot_handle: |
return_to_32bit(regs, VM86_INTx + (i << 8)); |
} |
|
|
|
int handle_vm86_trap(struct vm86_regs * regs, long error_code, int trapno) |
{ |
if (VMPI.is_vm86pus) { |
if ( (trapno==3) || (trapno==1) ) |
return_to_32bit(regs, VM86_TRAP + (trapno << 8)); |
do_int(regs, trapno, (unsigned char *) (regs->ss << 4), SP(regs)); |
return 0; |
} |
if (trapno !=1) |
return 1; /* we let this handle by the calling routine */ |
if (current->flags & PF_PTRACED) |
current->blocked &= ~(1 << (SIGTRAP-1)); |
send_sig(SIGTRAP, current, 1); |
current->tss.trap_no = trapno; |
current->tss.error_code = error_code; |
return 0; |
} |
|
|
void handle_vm86_fault(struct vm86_regs * regs, long error_code) |
{ |
unsigned char *csp, *ssp; |
unsigned long ip, sp; |
|
#define CHECK_IF_IN_TRAP \ |
if (VMPI.vm86dbg_active && VMPI.vm86dbg_TFpendig) \ |
pushw(ssp,sp,popw(ssp,sp) | TF_MASK); |
#define VM86_FAULT_RETURN \ |
if (VMPI.force_return_for_pic && (VEFLAGS & IF_MASK)) \ |
return_to_32bit(regs, VM86_PICRETURN); \ |
return; |
|
csp = (unsigned char *) (regs->cs << 4); |
ssp = (unsigned char *) (regs->ss << 4); |
sp = SP(regs); |
ip = IP(regs); |
|
switch (popb(csp, ip)) { |
|
/* operand size override */ |
case 0x66: |
switch (popb(csp, ip)) { |
|
/* pushfd */ |
case 0x9c: |
SP(regs) -= 4; |
IP(regs) += 2; |
pushl(ssp, sp, get_vflags(regs)); |
VM86_FAULT_RETURN; |
|
/* popfd */ |
case 0x9d: |
SP(regs) += 4; |
IP(regs) += 2; |
CHECK_IF_IN_TRAP |
set_vflags_long(popl(ssp, sp), regs); |
VM86_FAULT_RETURN; |
|
/* iretd */ |
case 0xcf: |
SP(regs) += 12; |
IP(regs) = (unsigned short)popl(ssp, sp); |
regs->cs = (unsigned short)popl(ssp, sp); |
CHECK_IF_IN_TRAP |
set_vflags_long(popl(ssp, sp), regs); |
VM86_FAULT_RETURN; |
/* need this to avoid a fallthrough */ |
default: |
return_to_32bit(regs, VM86_UNKNOWN); |
} |
|
/* pushf */ |
case 0x9c: |
SP(regs) -= 2; |
IP(regs)++; |
pushw(ssp, sp, get_vflags(regs)); |
VM86_FAULT_RETURN; |
|
/* popf */ |
case 0x9d: |
SP(regs) += 2; |
IP(regs)++; |
CHECK_IF_IN_TRAP |
set_vflags_short(popw(ssp, sp), regs); |
VM86_FAULT_RETURN; |
|
/* int xx */ |
case 0xcd: { |
int intno=popb(csp, ip); |
IP(regs) += 2; |
if (VMPI.vm86dbg_active) { |
if ( (1 << (intno &7)) & VMPI.vm86dbg_intxxtab[intno >> 3] ) |
return_to_32bit(regs, VM86_INTx + (intno << 8)); |
} |
do_int(regs, intno, ssp, sp); |
return; |
} |
|
/* iret */ |
case 0xcf: |
SP(regs) += 6; |
IP(regs) = popw(ssp, sp); |
regs->cs = popw(ssp, sp); |
CHECK_IF_IN_TRAP |
set_vflags_short(popw(ssp, sp), regs); |
VM86_FAULT_RETURN; |
|
/* cli */ |
case 0xfa: |
IP(regs)++; |
clear_IF(regs); |
VM86_FAULT_RETURN; |
|
/* sti */ |
/* |
* Damn. This is incorrect: the 'sti' instruction should actually |
* enable interrupts after the /next/ instruction. Not good. |
* |
* Probably needs some horsing around with the TF flag. Aiee.. |
*/ |
case 0xfb: |
IP(regs)++; |
set_IF(regs); |
VM86_FAULT_RETURN; |
|
default: |
return_to_32bit(regs, VM86_UNKNOWN); |
} |
} |
|
/* ---------------- vm86 special IRQ passing stuff ----------------- */ |
|
#define VM86_IRQNAME "vm86irq" |
|
static struct vm86_irqs { |
struct task_struct *tsk; |
int sig; |
} vm86_irqs[16] = {{0},}; |
static int irqbits=0; |
|
#define ALLOWED_SIGS ( 1 /* 0 = don't send a signal */ \ |
| (1 << SIGUSR1) | (1 << SIGUSR2) | (1 << SIGIO) | (1 << SIGURG) \ |
| (1 << SIGUNUSED) ) |
|
static void irq_handler(int intno, void *dev_id, struct pt_regs * regs) { |
int irq_bit; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
irq_bit = 1 << intno; |
if ((irqbits & irq_bit) || ! vm86_irqs[intno].tsk) { |
restore_flags(flags); |
return; |
} |
irqbits |= irq_bit; |
if (vm86_irqs[intno].sig) |
send_sig(vm86_irqs[intno].sig, vm86_irqs[intno].tsk, 1); |
/* else user will poll for IRQs */ |
restore_flags(flags); |
} |
|
static inline void free_vm86_irq(int irqnumber) |
{ |
free_irq(irqnumber,0); |
vm86_irqs[irqnumber].tsk = 0; |
irqbits &= ~(1 << irqnumber); |
} |
|
static inline int task_valid(struct task_struct *tsk) |
{ |
struct task_struct *p; |
|
for_each_task(p) { |
if ((p == tsk) && (p->sig)) return 1; |
} |
return 0; |
} |
|
static inline void handle_irq_zombies(void) |
{ |
int i; |
for (i=3; i<16; i++) { |
if (vm86_irqs[i].tsk) { |
if (task_valid(vm86_irqs[i].tsk)) continue; |
free_vm86_irq(i); |
} |
} |
} |
|
static inline int get_and_reset_irq(int irqnumber) |
{ |
int bit; |
unsigned long flags; |
|
if ( (irqnumber<3) || (irqnumber>15) ) return 0; |
if (vm86_irqs[irqnumber].tsk != current) return 0; |
save_flags(flags); |
cli(); |
bit = irqbits & (1 << irqnumber); |
irqbits &= ~bit; |
restore_flags(flags); |
return bit; |
} |
|
|
static int do_vm86_irq_handling(int subfunction, int irqnumber) |
{ |
int ret; |
switch (subfunction) { |
case VM86_GET_AND_RESET_IRQ: { |
return get_and_reset_irq(irqnumber); |
} |
case VM86_GET_IRQ_BITS: { |
return irqbits; |
} |
case VM86_REQUEST_IRQ: { |
int sig = irqnumber >> 8; |
int irq = irqnumber & 255; |
handle_irq_zombies(); |
if (!suser() || securelevel > 0) return -EPERM; |
if (!((1 << sig) & ALLOWED_SIGS)) return -EPERM; |
if ( (irq<3) || (irq>15) ) return -EPERM; |
if (vm86_irqs[irq].tsk) return -EPERM; |
ret = request_irq(irq, &irq_handler, 0, VM86_IRQNAME, 0); |
if (ret) return ret; |
vm86_irqs[irq].sig = sig; |
vm86_irqs[irq].tsk = current; |
return irq; |
} |
case VM86_FREE_IRQ: { |
handle_irq_zombies(); |
if ( (irqnumber<3) || (irqnumber>15) ) return -EPERM; |
if (!vm86_irqs[irqnumber].tsk) return 0; |
if (vm86_irqs[irqnumber].tsk != current) return -EPERM; |
free_vm86_irq(irqnumber); |
return 0; |
} |
} |
return -EINVAL; |
} |
|
/setup.c
0,0 → 1,553
/* |
* linux/arch/i386/kernel/setup.c |
* |
* Copyright (C) 1995 Linus Torvalds |
*/ |
|
/* |
* This file handles the architecture-dependent parts of initialization |
*/ |
|
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/mm.h> |
#include <linux/stddef.h> |
#include <linux/unistd.h> |
#include <linux/ptrace.h> |
#include <linux/malloc.h> |
#include <linux/ldt.h> |
#include <linux/user.h> |
#include <linux/a.out.h> |
#include <linux/tty.h> |
#include <linux/ioport.h> |
#include <linux/delay.h> |
#include <linux/config.h> |
#ifdef CONFIG_APM |
#include <linux/apm_bios.h> |
#endif |
#ifdef CONFIG_BLK_DEV_RAM |
#include <linux/blk.h> |
#endif |
#include <asm/segment.h> |
#include <asm/system.h> |
#include <asm/smp.h> |
#include <asm/io.h> |
|
/* |
* Tell us the machine setup.. |
*/ |
char hard_math = 0; /* set by kernel/head.S */ |
char x86 = 0; /* set by kernel/head.S to 3..6 */ |
char x86_model = 0; /* set by kernel/head.S */ |
char x86_mask = 0; /* set by kernel/head.S */ |
int x86_capability = 0; /* set by kernel/head.S */ |
int x86_ext_capability = 0; /* newer CPUs have this */ |
int fdiv_bug = 0; /* set if Pentium(TM) with FP bug */ |
int pentium_f00f_bug = 0; /* set if Pentium(TM) with F00F bug */ |
int have_cpuid = 0; /* set if CPUID instruction works */ |
int ext_cpuid = 0; /* if != 0, highest available CPUID value */ |
|
char x86_vendor_id[13] = "GenuineIntel";/* default */ |
|
static char *Cx86_step = "unknown"; /* stepping info for Cyrix CPUs */ |
|
static unsigned char Cx86_mult = 0; /* clock multiplier for Cyrix CPUs */ |
|
static const char *x86_clkmult[] = { |
"unknown", "1", "1.5", "2", "2.5", "3", "3.5", "4", "4.5", "5", "5.5", |
"6", "6.5", "7", "7.5", "8" |
}; |
|
char ignore_irq13 = 0; /* set if exception 16 works */ |
char wp_works_ok = -1; /* set if paging hardware honours WP */ |
char hlt_works_ok = 1; /* set if the "hlt" instruction works */ |
|
/* |
* Bus types .. |
*/ |
int EISA_bus = 0; |
|
/* |
* Setup options |
*/ |
struct drive_info_struct { char dummy[32]; } drive_info; |
struct screen_info screen_info; |
#ifdef CONFIG_APM |
struct apm_bios_info apm_bios_info; |
#endif |
|
unsigned char aux_device_present; |
|
#ifdef CONFIG_BLK_DEV_RAM |
extern int rd_doload; /* 1 = load ramdisk, 0 = don't load */ |
extern int rd_prompt; /* 1 = prompt for ramdisk, 0 = don't prompt */ |
extern int rd_image_start; /* starting block # of image */ |
#endif |
|
extern int root_mountflags; |
extern int _etext, _edata, _end; |
|
extern char empty_zero_page[PAGE_SIZE]; |
|
/* |
* This is set up by the setup-routine at boot-time |
*/ |
#define PARAM empty_zero_page |
#define EXT_MEM_K (*(unsigned short *) (PARAM+2)) |
#ifndef STANDARD_MEMORY_BIOS_CALL |
#define ALT_MEM_K (*(unsigned long *) (PARAM+0x1e0)) |
#endif |
#define APM_BIOS_INFO (*(struct apm_bios_info *) (PARAM+0x40)) |
#define DRIVE_INFO (*(struct drive_info_struct *) (PARAM+0x80)) |
#define SCREEN_INFO (*(struct screen_info *) (PARAM+0)) |
#define MOUNT_ROOT_RDONLY (*(unsigned short *) (PARAM+0x1F2)) |
#define RAMDISK_FLAGS (*(unsigned short *) (PARAM+0x1F8)) |
#define ORIG_ROOT_DEV (*(unsigned short *) (PARAM+0x1FC)) |
#define AUX_DEVICE_INFO (*(unsigned char *) (PARAM+0x1FF)) |
#define LOADER_TYPE (*(unsigned char *) (PARAM+0x210)) |
#define KERNEL_START (*(unsigned long *) (PARAM+0x214)) |
#define INITRD_START (*(unsigned long *) (PARAM+0x218)) |
#define INITRD_SIZE (*(unsigned long *) (PARAM+0x21c)) |
#define COMMAND_LINE ((char *) (PARAM+2048)) |
#define COMMAND_LINE_SIZE 256 |
|
#define RAMDISK_IMAGE_START_MASK 0x07FF |
#define RAMDISK_PROMPT_FLAG 0x8000 |
#define RAMDISK_LOAD_FLAG 0x4000 |
|
static char command_line[COMMAND_LINE_SIZE] = { 0, }; |
char saved_command_line[COMMAND_LINE_SIZE]; |
|
void setup_arch(char **cmdline_p, |
unsigned long * memory_start_p, unsigned long * memory_end_p) |
{ |
unsigned long memory_start, memory_end; |
#ifndef STANDARD_MEMORY_BIOS_CALL |
unsigned long memory_alt_end; |
#endif |
char c = ' ', *to = command_line, *from = COMMAND_LINE; |
int len = 0; |
static unsigned char smptrap=0; |
|
if(smptrap==1) |
{ |
return; |
} |
smptrap=1; |
|
ROOT_DEV = to_kdev_t(ORIG_ROOT_DEV); |
drive_info = DRIVE_INFO; |
screen_info = SCREEN_INFO; |
#ifdef CONFIG_APM |
apm_bios_info = APM_BIOS_INFO; |
#endif |
aux_device_present = AUX_DEVICE_INFO; |
memory_end = (1<<20) + (EXT_MEM_K<<10); |
#ifndef STANDARD_MEMORY_BIOS_CALL |
memory_alt_end = (1<<20) + (ALT_MEM_K<<10); |
if (memory_alt_end > memory_end) { |
printk("Memory: sized by int13 0e801h\n"); |
memory_end = memory_alt_end; |
} |
else |
printk("Memory: sized by int13 088h\n"); |
#endif |
memory_end &= PAGE_MASK; |
#ifdef CONFIG_BLK_DEV_RAM |
rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK; |
rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0); |
rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0); |
#endif |
#ifdef CONFIG_MAX_16M |
if (memory_end > 16*1024*1024) |
memory_end = 16*1024*1024; |
#endif |
|
/* |
* The CONFIG_MAX_MEMSIZE sanity checker. |
*/ |
|
if (memory_end > (CONFIG_MAX_MEMSIZE-128)*1024*1024) |
{ |
memory_end = (CONFIG_MAX_MEMSIZE-128)*1024*1024; |
printk(KERN_WARNING "ONLY %dMB RAM will be used, see Documentation/more-than-900MB-RAM.txt!.\n", CONFIG_MAX_MEMSIZE-128); |
udelay(3*1000*1000); |
} |
|
if (!MOUNT_ROOT_RDONLY) |
root_mountflags &= ~MS_RDONLY; |
memory_start = (unsigned long) &_end; |
init_task.mm->start_code = TASK_SIZE; |
init_task.mm->end_code = TASK_SIZE + (unsigned long) &_etext; |
init_task.mm->end_data = TASK_SIZE + (unsigned long) &_edata; |
init_task.mm->brk = TASK_SIZE + (unsigned long) &_end; |
|
/* Save unparsed command line copy for /proc/cmdline */ |
memcpy(saved_command_line, COMMAND_LINE, COMMAND_LINE_SIZE); |
saved_command_line[COMMAND_LINE_SIZE-1] = '\0'; |
|
for (;;) { |
/* |
* "mem=nopentium" disables the 4MB page tables. |
* "mem=XXX[kKmM]" overrides the BIOS-reported |
* memory size |
*/ |
if (c == ' ' && *(const unsigned long *)from == *(const unsigned long *)"mem=") { |
if (to != command_line) to--; |
if (!memcmp(from+4, "nopentium", 9)) { |
from += 9+4; |
x86_capability &= ~8; |
} else { |
memory_end = simple_strtoul(from+4, &from, 0); |
if ( *from == 'K' || *from == 'k' ) { |
memory_end = memory_end << 10; |
from++; |
} else if ( *from == 'M' || *from == 'm' ) { |
memory_end = memory_end << 20; |
from++; |
} |
} |
} |
c = *(from++); |
if (!c) |
break; |
if (COMMAND_LINE_SIZE <= ++len) |
break; |
*(to++) = c; |
} |
*to = '\0'; |
*cmdline_p = command_line; |
*memory_start_p = memory_start; |
*memory_end_p = memory_end; |
|
#ifdef CONFIG_BLK_DEV_INITRD |
if (LOADER_TYPE) { |
initrd_start = INITRD_START; |
initrd_end = INITRD_START+INITRD_SIZE; |
if (initrd_end > memory_end) { |
printk("initrd extends beyond end of memory " |
"(0x%08lx > 0x%08lx)\ndisabling initrd\n", |
initrd_end,memory_end); |
initrd_start = 0; |
} |
} |
#endif |
|
/* request io space for devices used on all i[345]86 PC'S */ |
request_region(0x00,0x20,"dma1"); |
request_region(0x40,0x20,"timer"); |
request_region(0x80,0x20,"dma page reg"); |
request_region(0xc0,0x20,"dma2"); |
request_region(0xf0,0x10,"npu"); |
} |
|
static const char * IDTmodel(void) |
/* Right now IDT has a single CPU model in production: the C6. |
* Adjust this when IDT/Centaur comes out with a new CPU model. |
* Stepping information is correctly reported in x86_mask. |
*/ |
{ |
static const char *model[] = { |
"C6", "C6-3D" |
}; |
return model[0]; |
} |
|
static const char * Cx86model(void) |
/* We know our CPU is a Cyrix now (see bugs.h), so we can use the DIR0/DIR1 |
* mechanism to figure out the model, bus clock multiplier and stepping. |
* For the newest CPUs (GXm and MXi) we use the Extended CPUID function. |
*/ |
{ |
unsigned char nr6x86 = 0; |
unsigned char cx_dir0 = 0; /* Model and bus clock multiplier */ |
unsigned char cx_dir1 = 0; /* Stepping info */ |
unsigned int flags; |
static const char *model[] = { |
"unknown", "Cx486", "5x86", "MediaGX", "6x86", "6x86L", "6x86MX", |
"M II" |
}; |
|
if (x86_model == -1) { /* is this an old Cx486 without DIR0/DIR1? */ |
nr6x86 = 1; /* Cx486 */ |
Cx86_mult = 0; /* unknown multiplier */ |
} |
else { |
|
/* Get DIR0, DIR1 since all other Cyrix CPUs have them */ |
|
save_flags(flags); |
cli(); |
cx_dir0 = getCx86(CX86_DIR0); /* we use the access macros */ |
cx_dir1 = getCx86(CX86_DIR1); /* defined in processor.h */ |
restore_flags(flags); |
|
/* Now cook; the recipe is by Channing Corn, from Cyrix. |
* We do the same thing for each generation: we work out |
* the model, multiplier and stepping. |
*/ |
|
if (cx_dir0 < 0x20) { |
nr6x86 = 1; /* Cx486 */ |
Cx86_mult = 0; /* unknown multiplier */ |
sprintf(Cx86_step, "%d.%d", (cx_dir1 >> 4) + 1, cx_dir1 & 0x0f); |
} |
|
if ((cx_dir0 > 0x20) && (cx_dir0 < 0x30)) { |
nr6x86 = 2; /* 5x86 */ |
Cx86_mult = ((cx_dir0 & 0x04) ? 5 : 3); /* either 3x or 2x */ |
sprintf(Cx86_step, "%d.%d", (cx_dir1 >> 4) + 1, cx_dir1 & 0x0f); |
} |
|
if ((cx_dir0 >= 0x30) && (cx_dir0 < 0x38)) { |
nr6x86 = ((x86_capability & (1 << 8)) ? 5 : 4); /* 6x86(L) */ |
Cx86_mult = ((cx_dir0 & 0x04) ? 5 : 3); /* either 3x or 2x */ |
sprintf(Cx86_step, "%d.%d", (cx_dir1 >> 3), cx_dir1 & 0x0f); |
} |
|
if ((cx_dir0 >= 0x40) && (cx_dir0 < 0x50)) { |
if (x86 == 4) { /* MediaGX */ |
nr6x86 = 3; |
Cx86_mult = ((cx_dir0 & 0x01) ? 5 : 7); /* either 3x or 4x */ |
switch (cx_dir1 >> 4) { |
case (0) : |
case (1) : |
sprintf(Cx86_step, "2.%d", cx_dir1 & 0x0f); |
break; |
case (2) : |
sprintf(Cx86_step, "1.%d", cx_dir1 & 0x0f); |
break; |
default : |
break; |
} |
} /* endif MediaGX */ |
if (x86 == 5) { /* GXm */ |
char GXm_mult[8] = {7,11,7,11,13,15,13,9}; /* 4 to 8 */ |
ext_cpuid = 0x80000005; /* available */ |
Cx86_mult = GXm_mult[cx_dir0 & 0x0f]; |
sprintf(Cx86_step, "%d.%d", (cx_dir1 >> 4) - 1, cx_dir1 & 0x0f); |
} /* endif GXm */ |
} |
|
if ((cx_dir0 >= 0x50) && (cx_dir0 < 0x60)) { |
nr6x86 = ((cx_dir1 > 7) ? 7 : 6); /* 6x86Mx or M II */ |
Cx86_mult = (cx_dir0 & 0x07) + 2; /* 2 to 5 in 0.5 steps */ |
if (((cx_dir1 & 0x0f) > 4) || ((cx_dir1 >> 4) == 2)) cx_dir1 += 0x10; |
sprintf(Cx86_step, "%d.%d", (cx_dir1 >> 4) + 1, cx_dir1 & 0x0f); |
} |
} |
x86_mask = 1; /* we don't use it, but has to be set to something */ |
return model[nr6x86]; |
} |
|
struct cpu_model_info { |
int cpu_x86; |
char *model_names[16]; |
}; |
|
static struct cpu_model_info amd_models[] = { |
{ 4, |
{ NULL, NULL, NULL, "DX/2", NULL, NULL, NULL, "DX/2-WB", "DX/4", |
"DX/4-WB", NULL, NULL, NULL, NULL, "Am5x86-WT", "Am5x86-WB" }}, |
{ 5, |
{ "K5/SSA5 (PR-75, PR-90, PR-100)"}}, |
}; |
|
static const char * AMDmodel(void) |
{ |
const char *p=NULL; |
int i; |
|
if ((x86_model == 0) || (x86 == 4)) { |
for (i=0; i<sizeof(amd_models)/sizeof(struct cpu_model_info); i++) |
if (amd_models[i].cpu_x86 == x86) { |
p = amd_models[i].model_names[(int)x86_model]; |
break; |
} |
} |
else ext_cpuid = 0x80000005; /* available */ |
return p; |
} |
|
static struct cpu_model_info intel_models[] = { |
{ 4, |
{ "486 DX-25/33", "486 DX-50", "486 SX", "486 DX/2", "486 SL", |
"486 SX/2", NULL, "486 DX/2-WB", "486 DX/4", "486 DX/4-WB", NULL, |
NULL, NULL, NULL, NULL, NULL }}, |
{ 5, |
{ "Pentium 60/66 A-step", "Pentium 60/66", "Pentium 75+", |
"OverDrive PODP5V83", "Pentium MMX", NULL, NULL, |
"Mobile Pentium 75+", "Mobile Pentium MMX", NULL, NULL, NULL, |
NULL, NULL, NULL, NULL }}, |
{ 6, |
{ "Pentium Pro A-step", "Pentium Pro", NULL, "Pentium II (Klamath)", |
NULL, "Pentium II (Deschutes)", "Celeron (Mendocino)", NULL, NULL, NULL, NULL, NULL, |
NULL, NULL, NULL, NULL }}, |
}; |
|
static const char * Intelmodel(void) |
{ |
const char *p = "386 SX/DX"; /* default to a 386 */ |
int i; |
|
/* |
* Old 486SX has no CPU ID. Set the model to 2 for this |
* case. |
*/ |
|
if( x86==4 && x86_model == 0 && hard_math == 0) |
x86_model = 2; |
|
for (i=0; i<sizeof(intel_models)/sizeof(struct cpu_model_info); i++) |
if (intel_models[i].cpu_x86 == x86) { |
p = intel_models[i].model_names[(int)x86_model]; |
break; |
} |
|
|
return p; |
} |
|
/* Recent Intel CPUs have an EEPROM and a ROM with CPU information. We'll use |
* this information in future versions of this code. |
* AMD and more recently Cyrix have decided to standardize on an extended |
* cpuid mechanism for their CPUs. |
*/ |
|
static const char * get_cpu_mkt_name(void) |
{ |
static char mktbuf[48]; |
int dummy; |
unsigned int *v; |
v = (unsigned int *) mktbuf; |
cpuid(0x80000002, &v[0], &v[1], &v[2], &v[3]); /* name, flags */ |
cpuid(0x80000003, &v[4], &v[5], &v[6], &v[7]); |
cpuid(0x80000004, &v[8], &v[9], &v[10], &v[11]); |
cpuid(0x80000001, &dummy, &dummy, &dummy, &x86_ext_capability); |
return mktbuf; |
} |
|
static const char * getmodel(void) |
/* Default is Intel. We disregard Nexgen processors. */ |
{ |
const char *p = NULL; |
if (strcmp(x86_vendor_id, "AuthenticAMD") == 0) /* AuthenticAMD */ |
p = AMDmodel(); |
else if (strcmp(x86_vendor_id, "CyrixInstead") == 0) /* CyrixInstead */ |
p = Cx86model(); |
else if (strcmp(x86_vendor_id, "CentaurHauls") == 0) /* CentaurHauls */ |
p = IDTmodel(); |
/* This isnt quite right */ |
else if (strcmp(x86_vendor_id, "UMC UMC UMC ") == 0) /* UMC */ |
p = Intelmodel(); |
else /* default - this could be anyone */ |
p = Intelmodel(); |
if (ext_cpuid) |
return get_cpu_mkt_name(); |
else |
return p; |
} |
|
int get_cpuinfo(char * buffer) |
{ |
int i, len = 0; |
static const char *x86_cap_flags[] = { |
"fpu", "vme", "de", "pse", "tsc", "msr", "pae", "mce", |
"cx8", "apic", "10", "sep", "mtrr", "pge", "mca", "cmov", |
"16", "17", "18", "19", "20", "21", "22", "mmx", |
"24", "25", "26", "27", "28", "29", "30", "31" |
}; |
static const char *x86_ext_cap_flags[] = { |
"fpu","vme", "de", "pse", "tsc", "msr", "6", "mce", |
"cx8", "9", "10", "syscr", "12", "pge", "14", "cmov", |
"fpcmov", "17", "psn", "19", "20", "21", "22", "mmx", |
"emmx", "25", "26", "27", "28", "29", "30", "3dnow" |
}; |
|
#ifdef __SMP__ |
int n; |
|
#define CD(X) (cpu_data[n].X) |
/* SMP has the wrong name for loops_per_sec */ |
#define loops_per_sec udelay_val |
#define CPUN n |
|
for ( n = 0 ; n < 32 ; n++ ) { |
if ( cpu_present_map & (1<<n) ) { |
if (len) buffer[len++] = '\n'; |
|
#else |
#define CD(X) (X) |
#define CPUN 0 |
#endif |
|
len += sprintf(buffer+len,"processor\t: %d\n" |
"cpu\t\t: %c86\n" |
"model\t\t: %s", |
CPUN, |
CD(x86)+'0', |
getmodel()); |
len += sprintf(buffer+len, |
"\nvendor_id\t: %s\n", |
x86_vendor_id); |
|
if (CD(x86_mask) || have_cpuid) |
if ((strncmp(x86_vendor_id, "Au", 2) == 0) |
&& (x86_model >= 6)) { |
len += sprintf(buffer+len, |
"stepping\t: %c\n", |
x86_mask + 'A'); |
} |
else if (strncmp(x86_vendor_id, "Cy", 2) == 0) { |
len += sprintf(buffer+len, |
"stepping\t: %s, core/bus clock ratio: %sx\n", |
Cx86_step, x86_clkmult[Cx86_mult]); |
} |
else { |
len += sprintf(buffer+len, |
"stepping\t: %d\n", |
CD(x86_mask)); |
} |
else |
len += sprintf(buffer+len, |
"stepping\t: unknown\n"); |
|
len += sprintf(buffer+len, |
"fdiv_bug\t: %s\n" |
"hlt_bug\t\t: %s\n" |
"f00f_bug\t: %s\n" |
"fpu\t\t: %s\n" |
"fpu_exception\t: %s\n" |
"cpuid\t\t: %s\n" |
"wp\t\t: %s\n" |
"flags\t\t:", |
CD(fdiv_bug) ? "yes" : "no", |
CD(hlt_works_ok) ? "no" : "yes", |
pentium_f00f_bug ? "yes" : "no", |
CD(hard_math) ? "yes" : "no", |
(CD(hard_math) && ignore_irq13) |
? "yes" : "no", |
CD(have_cpuid) ? "yes" : "no", |
CD(wp_works_ok) ? "yes" : "no"); |
|
for ( i = 0 ; i < 32 ; i++ ) { |
if ( CD(x86_capability) & (1 << i) ) { |
len += sprintf(buffer+len, " %s", |
x86_cap_flags[i]); |
} |
else if ( CD(x86_ext_capability) & (1 << i) ) { |
len += sprintf(buffer+len, " %s", |
x86_ext_cap_flags[i]); |
} |
} |
len += sprintf(buffer+len, |
"\nbogomips\t: %lu.%02lu\n", |
CD(loops_per_sec+2500)/500000, |
(CD(loops_per_sec+2500)/5000) % 100); |
#ifdef __SMP__ |
} |
} |
#endif |
return len; |
} |
/bios32.c
0,0 → 1,914
/* |
* bios32.c - BIOS32, PCI BIOS functions. |
* |
* $Id: bios32.c,v 1.1 2005-12-20 09:42:23 jcastillo Exp $ |
* |
* Sponsored by |
* iX Multiuser Multitasking Magazine |
* Hannover, Germany |
* hm@ix.de |
* |
* Copyright 1993, 1994 Drew Eckhardt |
* Visionary Computing |
* (Unix and Linux consulting and custom programming) |
* Drew@Colorado.EDU |
* +1 (303) 786-7975 |
* |
* For more information, please consult |
* |
* PCI BIOS Specification Revision |
* PCI Local Bus Specification |
* PCI System Design Guide |
* |
* PCI Special Interest Group |
* M/S HF3-15A |
* 5200 N.E. Elam Young Parkway |
* Hillsboro, Oregon 97124-6497 |
* +1 (503) 696-2000 |
* +1 (800) 433-5177 |
* |
* Manuals are $25 each or $50 for all three, plus $7 shipping |
* within the United States, $35 abroad. |
* |
* |
* CHANGELOG : |
* Jun 17, 1994 : Modified to accommodate the broken pre-PCI BIOS SPECIFICATION |
* Revision 2.0 present on <thys@dennis.ee.up.ac.za>'s ASUS mainboard. |
* |
* Jan 5, 1995 : Modified to probe PCI hardware at boot time by Frederic |
* Potter, potter@cao-vlsi.ibp.fr |
* |
* Jan 10, 1995 : Modified to store the information about configured pci |
* devices into a list, which can be accessed via /proc/pci by |
* Curtis Varner, cvarner@cs.ucr.edu |
* |
* Jan 12, 1995 : CPU-PCI bridge optimization support by Frederic Potter. |
* Alpha version. Intel & UMC chipset support only. |
* |
* Apr 16, 1995 : Source merge with the DEC Alpha PCI support. Most of the code |
* moved to drivers/pci/pci.c. |
* |
* Dec 7, 1996 : Added support for direct configuration access of boards |
* with Intel compatible access schemes (tsbogend@alpha.franken.de) |
* |
* Feb 3, 1997 : Set internal functions to static, save/restore flags |
* avoid dead locks reading broken PCI BIOS, werner@suse.de |
* |
* Apr 26, 1997 : Fixed case when there is BIOS32, but not PCI BIOS |
* (mj@atrey.karlin.mff.cuni.cz) |
* |
* May 7, 1997 : Added some missing cli()'s. [mj] |
* |
* Jun 20, 1997 : Corrected problems in "conf1" type accesses. |
* (paubert@iram.es) |
*/ |
|
#include <linux/config.h> |
#include <linux/types.h> |
#include <linux/kernel.h> |
#include <linux/bios32.h> |
#include <linux/pci.h> |
|
#include <asm/segment.h> |
#include <asm/system.h> |
#include <asm/io.h> |
|
#define PCIBIOS_PCI_FUNCTION_ID 0xb1XX |
#define PCIBIOS_PCI_BIOS_PRESENT 0xb101 |
#define PCIBIOS_FIND_PCI_DEVICE 0xb102 |
#define PCIBIOS_FIND_PCI_CLASS_CODE 0xb103 |
#define PCIBIOS_GENERATE_SPECIAL_CYCLE 0xb106 |
#define PCIBIOS_READ_CONFIG_BYTE 0xb108 |
#define PCIBIOS_READ_CONFIG_WORD 0xb109 |
#define PCIBIOS_READ_CONFIG_DWORD 0xb10a |
#define PCIBIOS_WRITE_CONFIG_BYTE 0xb10b |
#define PCIBIOS_WRITE_CONFIG_WORD 0xb10c |
#define PCIBIOS_WRITE_CONFIG_DWORD 0xb10d |
|
|
/* BIOS32 signature: "_32_" */ |
#define BIOS32_SIGNATURE (('_' << 0) + ('3' << 8) + ('2' << 16) + ('_' << 24)) |
|
/* PCI signature: "PCI " */ |
#define PCI_SIGNATURE (('P' << 0) + ('C' << 8) + ('I' << 16) + (' ' << 24)) |
|
/* PCI service signature: "$PCI" */ |
#define PCI_SERVICE (('$' << 0) + ('P' << 8) + ('C' << 16) + ('I' << 24)) |
|
/* |
* This is the standard structure used to identify the entry point |
* to the BIOS32 Service Directory, as documented in |
* Standard BIOS 32-bit Service Directory Proposal |
* Revision 0.4 May 24, 1993 |
* Phoenix Technologies Ltd. |
* Norwood, MA |
* and the PCI BIOS specification. |
*/ |
|
union bios32 { |
struct { |
unsigned long signature; /* _32_ */ |
unsigned long entry; /* 32 bit physical address */ |
unsigned char revision; /* Revision level, 0 */ |
unsigned char length; /* Length in paragraphs should be 01 */ |
unsigned char checksum; /* All bytes must add up to zero */ |
unsigned char reserved[5]; /* Must be zero */ |
} fields; |
char chars[16]; |
}; |
|
#ifdef CONFIG_PCI |
/* |
* Physical address of the service directory. I don't know if we're |
* allowed to have more than one of these or not, so just in case |
* we'll make pcibios_present() take a memory start parameter and store |
* the array there. |
*/ |
|
static unsigned long bios32_entry = 0; |
static struct { |
unsigned long address; |
unsigned short segment; |
} bios32_indirect = { 0, KERNEL_CS }; |
|
|
/* |
* function table for accessing PCI configuration space |
*/ |
struct pci_access { |
int (*find_device)(unsigned short, unsigned short, unsigned short, unsigned char *, unsigned char *); |
int (*find_class)(unsigned int, unsigned short, unsigned char *, unsigned char *); |
int (*read_config_byte)(unsigned char, unsigned char, unsigned char, unsigned char *); |
int (*read_config_word)(unsigned char, unsigned char, unsigned char, unsigned short *); |
int (*read_config_dword)(unsigned char, unsigned char, unsigned char, unsigned int *); |
int (*write_config_byte)(unsigned char, unsigned char, unsigned char, unsigned char); |
int (*write_config_word)(unsigned char, unsigned char, unsigned char, unsigned short); |
int (*write_config_dword)(unsigned char, unsigned char, unsigned char, unsigned int); |
}; |
|
/* |
* pointer to selected PCI access function table |
*/ |
static struct pci_access *access_pci = NULL; |
|
|
|
/* |
* Returns the entry point for the given service, NULL on error |
*/ |
|
static unsigned long bios32_service(unsigned long service) |
{ |
unsigned char return_code; /* %al */ |
unsigned long address; /* %ebx */ |
unsigned long length; /* %ecx */ |
unsigned long entry; /* %edx */ |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%edi)" |
: "=a" (return_code), |
"=b" (address), |
"=c" (length), |
"=d" (entry) |
: "0" (service), |
"1" (0), |
"D" (&bios32_indirect)); |
restore_flags(flags); |
|
switch (return_code) { |
case 0: |
return address + entry; |
case 0x80: /* Not present */ |
printk("bios32_service(0x%lx) : not present\n", service); |
return 0; |
default: /* Shouldn't happen */ |
printk("bios32_service(0x%lx) : returned 0x%x, mail drew@colorado.edu\n", |
service, return_code); |
return 0; |
} |
} |
|
static long pcibios_entry = 0; |
static struct { |
unsigned long address; |
unsigned short segment; |
} pci_indirect = { 0, KERNEL_CS }; |
|
|
static int check_pcibios(void) |
{ |
unsigned long signature; |
unsigned char present_status; |
unsigned char major_revision; |
unsigned char minor_revision; |
unsigned long flags; |
int pack; |
|
if ((pcibios_entry = bios32_service(PCI_SERVICE))) { |
pci_indirect.address = pcibios_entry; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%edi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:\tshl $8, %%eax\n\t" |
"movw %%bx, %%ax" |
: "=d" (signature), |
"=a" (pack) |
: "1" (PCIBIOS_PCI_BIOS_PRESENT), |
"D" (&pci_indirect) |
: "bx", "cx"); |
restore_flags(flags); |
|
present_status = (pack >> 16) & 0xff; |
major_revision = (pack >> 8) & 0xff; |
minor_revision = pack & 0xff; |
if (present_status || (signature != PCI_SIGNATURE)) { |
printk ("pcibios_init : %s : BIOS32 Service Directory says PCI BIOS is present,\n" |
" but PCI_BIOS_PRESENT subfunction fails with present status of 0x%x\n" |
" and signature of 0x%08lx (%c%c%c%c). mail drew@Colorado.EDU\n", |
(signature == PCI_SIGNATURE) ? "WARNING" : "ERROR", |
present_status, signature, |
(char) (signature >> 0), (char) (signature >> 8), |
(char) (signature >> 16), (char) (signature >> 24)); |
|
if (signature != PCI_SIGNATURE) |
pcibios_entry = 0; |
} |
if (pcibios_entry) { |
printk ("pcibios_init : PCI BIOS revision %x.%02x entry at 0x%lx\n", |
major_revision, minor_revision, pcibios_entry); |
return 1; |
} |
} |
return 0; |
} |
|
|
static int pci_bios_find_class (unsigned int class_code, unsigned short index, |
unsigned char *bus, unsigned char *device_fn) |
{ |
unsigned long bx; |
unsigned long ret; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__ ("lcall (%%edi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=b" (bx), |
"=a" (ret) |
: "1" (PCIBIOS_FIND_PCI_CLASS_CODE), |
"c" (class_code), |
"S" ((int) index), |
"D" (&pci_indirect)); |
restore_flags(flags); |
*bus = (bx >> 8) & 0xff; |
*device_fn = bx & 0xff; |
return (int) (ret & 0xff00) >> 8; |
} |
|
|
static int pci_bios_find_device (unsigned short vendor, unsigned short device_id, |
unsigned short index, unsigned char *bus, unsigned char *device_fn) |
{ |
unsigned short bx; |
unsigned short ret; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%edi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=b" (bx), |
"=a" (ret) |
: "1" (PCIBIOS_FIND_PCI_DEVICE), |
"c" (device_id), |
"d" (vendor), |
"S" ((int) index), |
"D" (&pci_indirect)); |
restore_flags(flags); |
*bus = (bx >> 8) & 0xff; |
*device_fn = bx & 0xff; |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_read_config_byte(unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned char *value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=c" (*value), |
"=a" (ret) |
: "1" (PCIBIOS_READ_CONFIG_BYTE), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_read_config_word (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned short *value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=c" (*value), |
"=a" (ret) |
: "1" (PCIBIOS_READ_CONFIG_WORD), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_read_config_dword (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned int *value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=c" (*value), |
"=a" (ret) |
: "1" (PCIBIOS_READ_CONFIG_DWORD), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_write_config_byte (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned char value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=a" (ret) |
: "0" (PCIBIOS_WRITE_CONFIG_BYTE), |
"c" (value), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_write_config_word (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned short value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=a" (ret) |
: "0" (PCIBIOS_WRITE_CONFIG_WORD), |
"c" (value), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
static int pci_bios_write_config_dword (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned int value) |
{ |
unsigned long ret; |
unsigned long bx = (bus << 8) | device_fn; |
unsigned long flags; |
|
save_flags(flags); cli(); |
__asm__("lcall (%%esi)\n\t" |
"jc 1f\n\t" |
"xor %%ah, %%ah\n" |
"1:" |
: "=a" (ret) |
: "0" (PCIBIOS_WRITE_CONFIG_DWORD), |
"c" (value), |
"b" (bx), |
"D" ((long) where), |
"S" (&pci_indirect)); |
restore_flags(flags); |
return (int) (ret & 0xff00) >> 8; |
} |
|
/* |
* function table for BIOS32 access |
*/ |
static struct pci_access pci_bios_access = { |
pci_bios_find_device, |
pci_bios_find_class, |
pci_bios_read_config_byte, |
pci_bios_read_config_word, |
pci_bios_read_config_dword, |
pci_bios_write_config_byte, |
pci_bios_write_config_word, |
pci_bios_write_config_dword |
}; |
|
|
|
/* |
* Given the vendor and device ids, find the n'th instance of that device |
* in the system. |
*/ |
static int pci_direct_find_device (unsigned short vendor, unsigned short device_id, |
unsigned short index, unsigned char *bus, |
unsigned char *devfn) |
{ |
unsigned int curr = 0; |
struct pci_dev *dev; |
|
for (dev = pci_devices; dev; dev = dev->next) { |
if (dev->vendor == vendor && dev->device == device_id) { |
if (curr == index) { |
*devfn = dev->devfn; |
*bus = dev->bus->number; |
return PCIBIOS_SUCCESSFUL; |
} |
++curr; |
} |
} |
return PCIBIOS_DEVICE_NOT_FOUND; |
} |
|
|
/* |
* Given the class, find the n'th instance of that device |
* in the system. |
*/ |
static int pci_direct_find_class (unsigned int class_code, unsigned short index, |
unsigned char *bus, unsigned char *devfn) |
{ |
unsigned int curr = 0; |
struct pci_dev *dev; |
|
for (dev = pci_devices; dev; dev = dev->next) { |
if (dev->class == class_code) { |
if (curr == index) { |
*devfn = dev->devfn; |
*bus = dev->bus->number; |
return PCIBIOS_SUCCESSFUL; |
} |
++curr; |
} |
} |
return PCIBIOS_DEVICE_NOT_FOUND; |
} |
|
/* |
* Functions for accessing PCI configuration space with type 1 accesses |
*/ |
#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) | (device_fn << 8) | (where & ~3)) |
|
static int pci_conf1_read_config_byte(unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned char *value) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
*value = inb(0xCFC + (where&3)); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf1_read_config_word (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned short *value) |
{ |
unsigned long flags; |
|
if (where&1) return PCIBIOS_BAD_REGISTER_NUMBER; |
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
*value = inw(0xCFC + (where&2)); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf1_read_config_dword (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned int *value) |
{ |
unsigned long flags; |
|
if (where&3) return PCIBIOS_BAD_REGISTER_NUMBER; |
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
*value = inl(0xCFC); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf1_write_config_byte (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned char value) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
outb(value, 0xCFC + (where&3)); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf1_write_config_word (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned short value) |
{ |
unsigned long flags; |
|
if (where&1) return PCIBIOS_BAD_REGISTER_NUMBER; |
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
outw(value, 0xCFC + (where&2)); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf1_write_config_dword (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned int value) |
{ |
unsigned long flags; |
|
if (where&3) return PCIBIOS_BAD_REGISTER_NUMBER; |
save_flags(flags); cli(); |
outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); |
outl(value, 0xCFC); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
#undef CONFIG_CMD |
|
/* |
* functiontable for type 1 |
*/ |
static struct pci_access pci_direct_conf1 = { |
pci_direct_find_device, |
pci_direct_find_class, |
pci_conf1_read_config_byte, |
pci_conf1_read_config_word, |
pci_conf1_read_config_dword, |
pci_conf1_write_config_byte, |
pci_conf1_write_config_word, |
pci_conf1_write_config_dword |
}; |
|
/* |
* Functions for accessing PCI configuration space with type 2 accesses |
*/ |
#define IOADDR(devfn, where) ((0xC000 | ((devfn & 0x78) << 5)) + where) |
#define FUNC(devfn) (((devfn & 7) << 1) | 0xf0) |
|
static int pci_conf2_read_config_byte(unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned char *value) |
{ |
unsigned long flags; |
|
if (device_fn & 0x80) |
return PCIBIOS_DEVICE_NOT_FOUND; |
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
*value = inb(IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf2_read_config_word (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned short *value) |
{ |
unsigned long flags; |
|
if (device_fn & 0x80) |
return PCIBIOS_DEVICE_NOT_FOUND; |
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
*value = inw(IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf2_read_config_dword (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned int *value) |
{ |
unsigned long flags; |
|
if (device_fn & 0x80) |
return PCIBIOS_DEVICE_NOT_FOUND; |
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
*value = inl (IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf2_write_config_byte (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned char value) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
outb (value, IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf2_write_config_word (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned short value) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
outw (value, IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
static int pci_conf2_write_config_dword (unsigned char bus, unsigned char device_fn, |
unsigned char where, unsigned int value) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
outb (FUNC(device_fn), 0xCF8); |
outb (bus, 0xCFA); |
outl (value, IOADDR(device_fn,where)); |
outb (0, 0xCF8); |
restore_flags(flags); |
return PCIBIOS_SUCCESSFUL; |
} |
|
#undef IOADDR |
#undef FUNC |
|
/* |
* functiontable for type 2 |
*/ |
static struct pci_access pci_direct_conf2 = { |
pci_direct_find_device, |
pci_direct_find_class, |
pci_conf2_read_config_byte, |
pci_conf2_read_config_word, |
pci_conf2_read_config_dword, |
pci_conf2_write_config_byte, |
pci_conf2_write_config_word, |
pci_conf2_write_config_dword |
}; |
|
|
static struct pci_access *check_direct_pci(void) |
{ |
unsigned int tmp; |
unsigned long flags; |
|
save_flags(flags); cli(); |
|
/* |
* check if configuration type 1 works |
*/ |
outb (0x01, 0xCFB); |
tmp = inl (0xCF8); |
outl (0x80000000, 0xCF8); |
if (inl (0xCF8) == 0x80000000) { |
outl (tmp, 0xCF8); |
restore_flags(flags); |
printk("pcibios_init: Using configuration type 1\n"); |
return &pci_direct_conf1; |
} |
outl (tmp, 0xCF8); |
|
/* |
* check if configuration type 2 works |
*/ |
outb (0x00, 0xCFB); |
outb (0x00, 0xCF8); |
outb (0x00, 0xCFA); |
if (inb (0xCF8) == 0x00 && inb (0xCFA) == 0x00) { |
restore_flags(flags); |
printk("pcibios_init: Using configuration type 2\n"); |
return &pci_direct_conf2; |
} |
restore_flags(flags); |
printk("pcibios_init: Not supported chipset for direct PCI access !\n"); |
return NULL; |
} |
|
|
/* |
* access defined pcibios functions via |
* the function table |
*/ |
|
int pcibios_present(void) |
{ |
return access_pci ? 1 : 0; |
} |
|
int pcibios_find_class (unsigned int class_code, unsigned short index, |
unsigned char *bus, unsigned char *device_fn) |
{ |
if (access_pci && access_pci->find_class) |
return access_pci->find_class(class_code, index, bus, device_fn); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_find_device (unsigned short vendor, unsigned short device_id, |
unsigned short index, unsigned char *bus, unsigned char *device_fn) |
{ |
if (access_pci && access_pci->find_device) |
return access_pci->find_device(vendor, device_id, index, bus, device_fn); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_read_config_byte (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned char *value) |
{ |
if (access_pci && access_pci->read_config_byte) |
return access_pci->read_config_byte(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_read_config_word (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned short *value) |
{ |
if (access_pci && access_pci->read_config_word) |
return access_pci->read_config_word(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_read_config_dword (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned int *value) |
{ |
if (access_pci && access_pci->read_config_dword) |
return access_pci->read_config_dword(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_write_config_byte (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned char value) |
{ |
if (access_pci && access_pci->write_config_byte) |
return access_pci->write_config_byte(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_write_config_word (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned short value) |
{ |
if (access_pci && access_pci->write_config_word) |
return access_pci->write_config_word(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
int pcibios_write_config_dword (unsigned char bus, |
unsigned char device_fn, unsigned char where, unsigned int value) |
{ |
if (access_pci && access_pci->write_config_dword) |
return access_pci->write_config_dword(bus, device_fn, where, value); |
|
return PCIBIOS_FUNC_NOT_SUPPORTED; |
} |
|
const char *pcibios_strerror (int error) |
{ |
static char buf[80]; |
|
switch (error) { |
case PCIBIOS_SUCCESSFUL: |
return "SUCCESSFUL"; |
|
case PCIBIOS_FUNC_NOT_SUPPORTED: |
return "FUNC_NOT_SUPPORTED"; |
|
case PCIBIOS_BAD_VENDOR_ID: |
return "SUCCESSFUL"; |
|
case PCIBIOS_DEVICE_NOT_FOUND: |
return "DEVICE_NOT_FOUND"; |
|
case PCIBIOS_BAD_REGISTER_NUMBER: |
return "BAD_REGISTER_NUMBER"; |
|
case PCIBIOS_SET_FAILED: |
return "SET_FAILED"; |
|
case PCIBIOS_BUFFER_TOO_SMALL: |
return "BUFFER_TOO_SMALL"; |
|
default: |
sprintf (buf, "UNKNOWN RETURN 0x%x", error); |
return buf; |
} |
} |
|
|
unsigned long pcibios_fixup(unsigned long mem_start, unsigned long mem_end) |
{ |
return mem_start; |
} |
|
#endif |
|
unsigned long pcibios_init(unsigned long memory_start, unsigned long memory_end) |
{ |
#ifdef CONFIG_PCI |
union bios32 *check; |
unsigned char sum; |
int i, length; |
|
/* |
* Follow the standard procedure for locating the BIOS32 Service |
* directory by scanning the permissible address range from |
* 0xe0000 through 0xfffff for a valid BIOS32 structure. |
* |
*/ |
|
for (check = (union bios32 *) 0xe0000; |
check <= (union bios32 *) 0xffff0; |
++check) { |
if (check->fields.signature != BIOS32_SIGNATURE) |
continue; |
length = check->fields.length * 16; |
if (!length) |
continue; |
sum = 0; |
for (i = 0; i < length ; ++i) |
sum += check->chars[i]; |
if (sum != 0) |
continue; |
if (check->fields.revision != 0) { |
printk("pcibios_init : unsupported revision %d at 0x%p, mail drew@colorado.edu\n", |
check->fields.revision, check); |
continue; |
} |
printk ("pcibios_init : BIOS32 Service Directory structure at 0x%p\n", check); |
if (!bios32_entry) { |
if (check->fields.entry >= 0x100000) { |
printk("pcibios_init: entry in high memory, trying direct PCI access\n"); |
access_pci = check_direct_pci(); |
} else { |
bios32_entry = check->fields.entry; |
printk ("pcibios_init : BIOS32 Service Directory entry at 0x%lx\n", bios32_entry); |
bios32_indirect.address = bios32_entry; |
} |
} |
} |
if (bios32_entry && check_pcibios()) |
access_pci = &pci_bios_access; |
#endif |
return memory_start; |
} |
/ksyms.c
0,0 → 1,40
#include <linux/module.h> |
#include <linux/smp.h> |
#include <linux/user.h> |
#include <linux/elfcore.h> |
#include <linux/delay.h> |
|
#include <asm/semaphore.h> |
#include <asm/processor.h> |
#include <asm/ptrace.h> |
|
extern void dump_thread(struct pt_regs *, struct user *); |
extern int dump_fpu(elf_fpregset_t *); |
|
static struct symbol_table arch_symbol_table = { |
#include <linux/symtab_begin.h> |
/* platform dependent support */ |
X(x86_capability), |
X(dump_thread), |
X(dump_fpu), |
X(get_pt_regs_for_task), |
XNOVERS(__do_delay), |
XNOVERS(down_failed), |
XNOVERS(down_failed_interruptible), |
XNOVERS(up_wakeup), |
#ifdef __SMP__ |
X(apic_reg), /* Needed internally for the I386 inlines */ |
X(cpu_data), |
X(syscall_count), |
X(kernel_flag), |
X(kernel_counter), |
X(active_kernel_processor), |
X(smp_invalidate_needed), |
#endif |
#include <linux/symtab_end.h> |
}; |
|
void arch_syms_export(void) |
{ |
register_symtab(&arch_symbol_table); |
} |
/ioport.c
0,0 → 1,89
/* |
* linux/arch/i386/kernel/ioport.c |
* |
* This contains the io-permission bitmap code - written by obz, with changes |
* by Linus. |
*/ |
|
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/errno.h> |
#include <linux/types.h> |
#include <linux/ioport.h> |
|
/* Set EXTENT bits starting at BASE in BITMAP to value TURN_ON. */ |
static void set_bitmap(unsigned long *bitmap, short base, short extent, int new_value) |
{ |
int mask; |
unsigned long *bitmap_base = bitmap + (base >> 5); |
unsigned short low_index = base & 0x1f; |
int length = low_index + extent; |
|
if (low_index != 0) { |
mask = (~0 << low_index); |
if (length < 32) |
mask &= ~(~0 << length); |
if (new_value) |
*bitmap_base++ |= mask; |
else |
*bitmap_base++ &= ~mask; |
length -= 32; |
} |
|
mask = (new_value ? ~0 : 0); |
while (length >= 32) { |
*bitmap_base++ = mask; |
length -= 32; |
} |
|
if (length > 0) { |
mask = ~(~0 << length); |
if (new_value) |
*bitmap_base++ |= mask; |
else |
*bitmap_base++ &= ~mask; |
} |
} |
|
/* |
* this changes the io permissions bitmap in the current task. |
*/ |
asmlinkage int sys_ioperm(unsigned long from, unsigned long num, int turn_on) |
{ |
if (from + num <= from) |
return -EINVAL; |
if (from + num > IO_BITMAP_SIZE*32) |
return -EINVAL; |
if (!suser() || securelevel > 0) |
return -EPERM; |
|
set_bitmap((unsigned long *)current->tss.io_bitmap, from, num, !turn_on); |
return 0; |
} |
|
unsigned int *stack; |
|
/* |
* sys_iopl has to be used when you want to access the IO ports |
* beyond the 0x3ff range: to get the full 65536 ports bitmapped |
* you'd need 8kB of bitmaps/process, which is a bit excessive. |
* |
* Here we just change the eflags value on the stack: we allow |
* only the super-user to do it. This depends on the stack-layout |
* on system-call entry - see also fork() and the signal handling |
* code. |
*/ |
asmlinkage int sys_iopl(long ebx,long ecx,long edx, |
long esi, long edi, long ebp, long eax, long ds, |
long es, long fs, long gs, long orig_eax, |
long eip,long cs,long eflags,long esp,long ss) |
{ |
unsigned int level = ebx; |
|
if (level > 3) |
return -EINVAL; |
if (!suser() || securelevel > 0) |
return -EPERM; |
*(&eflags) = (eflags & 0xffffcfff) | (level << 12); |
return 0; |
} |
/apm.c
0,0 → 1,1269
/* -*- linux-c -*- |
* APM BIOS driver for Linux |
* Copyright 1994-1999 Stephen Rothwell |
* (Stephen.Rothwell@canb.auug.org.au) |
* Development of this driver was funded by NEC Australia P/L |
* and NEC Corporation |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License as published by the |
* Free Software Foundation; either version 2, or (at your option) any |
* later version. |
* |
* This program is distributed in the hope that it will be useful, but |
* WITHOUT ANY WARRANTY; without even the implied warranty of |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
* General Public License for more details. |
* |
* October 1995, Rik Faith (faith@cs.unc.edu): |
* Minor enhancements and updates (to the patch set) for 1.3.x |
* Documentation |
* January 1996, Rik Faith (faith@cs.unc.edu): |
* Make /proc/apm easy to format (bump driver version) |
* March 1996, Rik Faith (faith@cs.unc.edu): |
* Prohibit APM BIOS calls unless apm_enabled. |
* (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>) |
* April 1996, Stephen Rothwell (Stephen.Rothwell@canb.auug.org.au) |
* Version 1.0 and 1.1 |
* May 1996, Version 1.2 |
* |
* History: |
* 0.6b: first version in official kernel, Linux 1.3.46 |
* 0.7: changed /proc/apm format, Linux 1.3.58 |
* 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59 |
* 0.9: only call bios if bios is present, Linux 1.3.72 |
* 1.0: use fixed device number, consolidate /proc/apm into this file, |
* Linux 1.3.85 |
* 1.1: support user-space standby and suspend, power off after system |
* halted, Linux 1.3.98 |
* 1.2: When resetting RTC after resume, take care so that the time |
* is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth |
* <jtoth@princeton.edu>); improve interaction between |
* screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4 |
* 1.2a: Fix OOPs on power off with no APM BIOS |
* Jan Echternach <echter@informatik.uni-rostock.de> |
* |
* Reference: |
* |
* Intel Corporation, Microsoft Corporation. Advanced Power Management |
* (APM) BIOS Interface Specification, Revision 1.1, September 1993. |
* Intel Order Number 241704-001. Microsoft Part Number 781-110-X01. |
* |
* [This document is available free from Intel by calling 800.628.8686 (fax |
* 916.356.6100) or 800.548.4725; or via anonymous ftp from |
* ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc. It is also |
* available from Microsoft by calling 206.882.8080.] |
* |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
|
#include <asm/system.h> |
#include <asm/segment.h> |
|
#include <linux/types.h> |
#include <linux/stddef.h> |
#include <linux/timer.h> |
#include <linux/fcntl.h> |
#include <linux/malloc.h> |
#include <linux/linkage.h> |
#ifdef CONFIG_PROC_FS |
#include <linux/stat.h> |
#include <linux/proc_fs.h> |
#endif |
#include <linux/miscdevice.h> |
#include <linux/apm_bios.h> |
|
static struct symbol_table apm_syms = { |
#include <linux/symtab_begin.h> |
X(apm_register_callback), |
X(apm_unregister_callback), |
#include <linux/symtab_end.h> |
}; |
|
extern unsigned long get_cmos_time(void); |
|
/* |
* The apm_bios device is one of the misc char devices. |
* This is its minor number. |
*/ |
#define APM_MINOR_DEV 134 |
|
/* Configurable options: |
* |
* CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests. |
* This is necessary on the NEC Versa M series, which generates these when |
* resuming from SYSTEM SUSPEND. However, enabling this on other laptops |
* will cause the laptop to generate a CRITICAL SUSPEND when an appropriate |
* USER SUSPEND is ignored -- this may prevent the APM driver from updating |
* the system time on a RESUME. |
* |
* CONFIG_APM_DO_ENABLE: enable APM features at boot time. From page 36 of |
* the specification: "When disabled, the APM BIOS does not automatically |
* power manage devices, enter the Standby State, enter the Suspend State, |
* or take power saving steps in response to CPU Idle calls." This driver |
* will make CPU Idle calls when Linux is idle (unless this feature is |
* turned off -- see below). This should always save battery power, but |
* more complicated APM features will be dependent on your BIOS |
* implementation. You may need to turn this option off if your computer |
* hangs at boot time when using APM support, or if it beeps continuously |
* instead of suspending. Turn this off if you have a NEC UltraLite Versa |
* 33/C or a Toshiba T400CDT. This is off by default since most machines |
* do fine without this feature. |
* |
* CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the |
* idle loop. On some machines, this can activate improved power savings, |
* such as a slowed CPU clock rate, when the machine is idle. These idle |
* call is made after the idle loop has run for some length of time (e.g., |
* 333 mS). On some machines, this will cause a hang at boot time or |
* whenever the CPU becomes idle. |
* |
* CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM. Some |
* laptops can use this to turn of the LCD backlight when the VC screen |
* blanker blanks the screen. Note that this is only used by the VC screen |
* blanker, and probably won't turn off the backlight when using X11. Some |
* problems have been reported when using this option with gpm (if you'd |
* like to debug this, please do so). |
* |
* CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist |
* on returning multiple suspend/standby events whenever one occurs. We |
* really only need one at a time, so just ignore any beyond the first. |
* This is probably safe on most laptops. |
* |
* If you are debugging the APM support for your laptop, note that code for |
* all of these options is contained in this file, so you can #define or |
* #undef these on the next line to avoid recompiling the whole kernel. |
* |
*/ |
|
/* KNOWN PROBLEM MACHINES: |
* |
* U: TI 4000M TravelMate: BIOS is *NOT* APM compliant |
* [Confirmed by TI representative] |
* U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification |
* [Confirmed by BIOS disassembly] |
* P: Toshiba 1950S: battery life information only gets updated after resume |
* |
* Legend: U = unusable with APM patches |
* P = partially usable with APM patches |
*/ |
|
/* |
* Define to have debug messages. |
*/ |
#undef APM_DEBUG |
|
/* |
* Define to always call the APM BIOS busy routine even if the clock was |
* not slowed by the idle routine. |
*/ |
#define ALWAYS_CALL_BUSY |
|
/* |
* Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call |
* should turn interrupts on before it does a 'hlt'). |
*/ |
#define APM_NOINTS |
|
/* |
* Define to make the APM BIOS calls zero all data segment registers (do |
* that if an incorrect BIOS implementation will cause a kernel panic if it |
* tries to write to arbitrary memory). |
*/ |
#define APM_ZERO_SEGS |
|
/* |
* Define to make all set_limit calls use 64k limits. The APM 1.1 BIOS is |
* supposed to provide limit information that it recognizes. Many machines |
* do this correctly, but many others do not restrict themselves to their |
* claimed limit. When this happens, they will cause a segmentation |
* violation in the kernel at boot time. Most BIOS's, however, will |
* respect a 64k limit, so we use that. If you want to be pedantic and |
* hold your BIOS to its claims, then undefine this. |
*/ |
#define APM_RELAX_SEGMENTS |
|
/* |
* Need to poll the APM BIOS every second |
*/ |
#define APM_CHECK_TIMEOUT (HZ) |
|
/* |
* These are the actual BIOS calls in assembler. Depending on |
* APM_ZERO_SEGS and APM_NOINTS, we are being really paranoid here! Not |
* only are interrupts disabled, but all the segment registers (except SS) |
* are saved and zeroed this means that if the BIOS tries to reference any |
* data without explicitly loading the segment registers, the kernel will |
* fault immediately rather than have some unforeseen circumstances for the |
* rest of the kernel. And it will be very obvious! :-) Doing this |
* depends on CS referring to the same physical memory as DS so that DS can |
* be zeroed before the call. Unfortunately, we can't do anything about the |
* stack segment/pointer. Also, we tell the compiler that everything could |
* change. |
*/ |
#ifdef APM_NOINTS |
# define APM_DO_CLI "cli\n\t" |
#else |
# define APM_DO_CLI |
#endif |
#ifdef APM_ZERO_SEGS |
# define APM_DO_ZERO_SEGS \ |
"pushl %%ds\n\t" \ |
"pushl %%es\n\t" \ |
"pushl %%fs\n\t" \ |
"pushl %%gs\n\t" \ |
"xorl %%edx, %%edx\n\t" \ |
"mov %%dx, %%ds\n\t" \ |
"mov %%dx, %%es\n\t" \ |
"mov %%dx, %%fs\n\t" \ |
"mov %%dx, %%gs\n\t" |
# define APM_DO_RESTORE_SEGS \ |
"popl %%gs\n\t" \ |
"popl %%fs\n\t" \ |
"popl %%es\n\t" \ |
"popl %%ds\n\t" |
#else |
# define APM_DO_ZERO_SEGS |
# define APM_DO_RESTORE_SEGS |
#endif |
|
#define APM_BIOS_CALL(error_reg) \ |
__asm__ __volatile__( \ |
APM_DO_ZERO_SEGS \ |
"pushfl\n\t" \ |
APM_DO_CLI \ |
"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" \ |
"setc %%" # error_reg "\n\t" \ |
"popfl\n\t" \ |
APM_DO_RESTORE_SEGS |
#define APM_BIOS_CALL_END \ |
: "ax", "bx", "cx", "dx", "si", "di", "bp", "memory") |
|
#ifdef CONFIG_APM_CPU_IDLE |
#define APM_SET_CPU_IDLE(error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x5305) \ |
APM_BIOS_CALL_END |
#endif |
|
#define APM_SET_CPU_BUSY(error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x5306) \ |
APM_BIOS_CALL_END |
|
#define APM_SET_POWER_STATE(state, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x5307), "b" (0x0001), "c" (state) \ |
APM_BIOS_CALL_END |
|
#ifdef CONFIG_APM_DISPLAY_BLANK |
#define APM_SET_DISPLAY_POWER_STATE(state, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x5307), "b" (0x01ff), "c" (state) \ |
APM_BIOS_CALL_END |
#endif |
|
#ifdef CONFIG_APM_DO_ENABLE |
#define APM_ENABLE_POWER_MANAGEMENT(device, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x5308), "b" (device), "c" (1) \ |
APM_BIOS_CALL_END |
#endif |
|
#define APM_GET_POWER_STATUS(bx, cx, dx, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx) \ |
: "a" (0x530a), "b" (1) \ |
APM_BIOS_CALL_END |
|
#define APM_GET_EVENT(event, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error), "=b" (event) \ |
: "a" (0x530b) \ |
APM_BIOS_CALL_END |
|
#define APM_DRIVER_VERSION(ver, ax, error) \ |
APM_BIOS_CALL(bl) \ |
: "=a" (ax), "=b" (error) \ |
: "a" (0x530e), "b" (0), "c" (ver) \ |
APM_BIOS_CALL_END |
|
#define APM_ENGAGE_POWER_MANAGEMENT(device, error) \ |
APM_BIOS_CALL(al) \ |
: "=a" (error) \ |
: "a" (0x530f), "b" (device), "c" (1) \ |
APM_BIOS_CALL_END |
|
/* |
* Forward declarations |
*/ |
static void suspend(void); |
static void standby(void); |
static void set_time(void); |
|
static void check_events(void); |
static void do_apm_timer(unsigned long); |
|
static int do_open(struct inode *, struct file *); |
static void do_release(struct inode *, struct file *); |
static int do_read(struct inode *, struct file *, char *, int); |
static int do_select(struct inode *, struct file *, int, |
select_table *); |
static int do_ioctl(struct inode *, struct file *, u_int, u_long); |
|
#ifdef CONFIG_PROC_FS |
static int apm_get_info(char *, char **, off_t, int, int); |
#endif |
|
extern int apm_register_callback(int (*)(apm_event_t)); |
extern void apm_unregister_callback(int (*)(apm_event_t)); |
|
/* |
* Local variables |
*/ |
static asmlinkage struct { |
unsigned long offset; |
unsigned short segment; |
} apm_bios_entry; |
static int apm_enabled = 0; |
#ifdef CONFIG_APM_CPU_IDLE |
static int clock_slowed = 0; |
#endif |
static int suspends_pending = 0; |
static int standbys_pending = 0; |
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND |
static int waiting_for_resume = 0; |
#endif |
|
static long clock_cmos_diff; |
static int got_clock_diff = 0; |
|
static struct wait_queue * process_list = NULL; |
static struct apm_bios_struct * user_list = NULL; |
|
static struct timer_list apm_timer; |
|
static char driver_version[] = "1.2";/* no spaces */ |
|
#ifdef APM_DEBUG |
static char * apm_event_name[] = { |
"system standby", |
"system suspend", |
"normal resume", |
"critical resume", |
"low battery", |
"power status change", |
"update time", |
"critical suspend", |
"user standby", |
"user suspend", |
"system standby resume" |
}; |
#define NR_APM_EVENT_NAME \ |
(sizeof(apm_event_name) / sizeof(apm_event_name[0])) |
#endif |
|
static struct file_operations apm_bios_fops = { |
NULL, /* lseek */ |
do_read, |
NULL, /* write */ |
NULL, /* readdir */ |
do_select, |
do_ioctl, |
NULL, /* mmap */ |
do_open, |
do_release, |
NULL, /* fsync */ |
NULL /* fasync */ |
}; |
|
static struct miscdevice apm_device = { |
APM_MINOR_DEV, |
"apm", |
&apm_bios_fops |
}; |
|
#ifdef CONFIG_PROC_FS |
static struct proc_dir_entry apm_proc_entry = { |
0, 3, "apm", S_IFREG | S_IRUGO, 1, 0, 0, 0, 0, apm_get_info |
}; |
#endif |
|
typedef struct callback_list_t { |
int (* callback)(apm_event_t); |
struct callback_list_t * next; |
} callback_list_t; |
|
static callback_list_t * callback_list = NULL; |
|
typedef struct lookup_t { |
int key; |
char * msg; |
} lookup_t; |
|
static const lookup_t error_table[] = { |
/* N/A { APM_SUCCESS, "Operation succeeded" }, */ |
{ APM_DISABLED, "Power management disabled" }, |
{ APM_CONNECTED, "Real mode interface already connected" }, |
{ APM_NOT_CONNECTED, "Interface not connected" }, |
{ APM_16_CONNECTED, "16 bit interface already connected" }, |
/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */ |
{ APM_32_CONNECTED, "32 bit interface already connected" }, |
{ APM_32_UNSUPPORTED, "32 bit interface not supported" }, |
{ APM_BAD_DEVICE, "Unrecognized device ID" }, |
{ APM_BAD_PARAM, "Parameter out of range" }, |
{ APM_NOT_ENGAGED, "Interface not engaged" }, |
{ APM_BAD_STATE, "Unable to enter requested state" }, |
/* N/A { APM_NO_EVENTS, "No events pending" }, */ |
{ APM_NOT_PRESENT, "No APM present" } |
}; |
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t)) |
|
static int apm_driver_version(u_short *val) |
{ |
u_short error; |
|
APM_DRIVER_VERSION(*val, *val, error); |
|
if (error & 0xff) |
return (*val >> 8); |
return APM_SUCCESS; |
} |
|
static int apm_get_event(apm_event_t *event) |
{ |
u_short error; |
|
APM_GET_EVENT(*event, error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
|
static int apm_set_power_state(u_short state) |
{ |
u_short error; |
|
APM_SET_POWER_STATE(state, error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
|
#ifdef CONFIG_APM_POWER_OFF |
void apm_power_off(void) |
{ |
if (apm_enabled) |
(void) apm_set_power_state(APM_STATE_OFF); |
} |
#endif |
|
#ifdef CONFIG_APM_DISPLAY_BLANK |
/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */ |
static int apm_set_display_power_state(u_short state) |
{ |
u_short error; |
|
APM_SET_DISPLAY_POWER_STATE(state, error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
#endif |
|
#ifdef CONFIG_APM_DO_ENABLE |
/* Called by apm_setup if apm_enabled will be true. */ |
static int apm_enable_power_management(void) |
{ |
u_short error; |
|
APM_ENABLE_POWER_MANAGEMENT((apm_bios_info.version > 0x100) |
? 0x0001 : 0xffff, |
error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
#endif |
|
static int apm_get_power_status(u_short *status, u_short *bat, u_short *life) |
{ |
u_short error; |
|
APM_GET_POWER_STATUS(*status, *bat, *life, error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
|
static int apm_engage_power_management(u_short device) |
{ |
u_short error; |
|
APM_ENGAGE_POWER_MANAGEMENT(device, error); |
if (error & 0xff) |
return (error >> 8); |
return APM_SUCCESS; |
} |
|
static void apm_error(char *str, int err) |
{ |
int i; |
|
for (i = 0; i < ERROR_COUNT; i++) |
if (error_table[i].key == err) break; |
if (i < ERROR_COUNT) |
printk("apm_bios: %s: %s\n", str, error_table[i].msg); |
else |
printk("apm_bios: %s: unknown error code %#2.2x\n", str, err); |
} |
|
/* Called from console driver -- must make sure apm_enabled. */ |
int apm_display_blank(void) |
{ |
#ifdef CONFIG_APM_DISPLAY_BLANK |
int error; |
|
if (!apm_enabled) |
return 0; |
error = apm_set_display_power_state(APM_STATE_STANDBY); |
if (error == APM_SUCCESS) |
return 1; |
apm_error("set display standby", error); |
#endif |
return 0; |
} |
|
/* Called from console driver -- must make sure apm_enabled. */ |
int apm_display_unblank(void) |
{ |
#ifdef CONFIG_APM_DISPLAY_BLANK |
int error; |
|
if (!apm_enabled) |
return 0; |
error = apm_set_display_power_state(APM_STATE_READY); |
if (error == APM_SUCCESS) |
return 1; |
apm_error("set display ready", error); |
#endif |
return 0; |
} |
|
int apm_register_callback(int (*callback)(apm_event_t)) |
{ |
callback_list_t * new; |
|
new = kmalloc(sizeof(callback_list_t), GFP_KERNEL); |
if (new == NULL) |
return -ENOMEM; |
new->callback = callback; |
new->next = callback_list; |
callback_list = new; |
return 0; |
} |
|
void apm_unregister_callback(int (*callback)(apm_event_t)) |
{ |
callback_list_t ** ptr; |
callback_list_t * old; |
|
ptr = &callback_list; |
for (ptr = &callback_list; *ptr != NULL; ptr = &(*ptr)->next) |
if ((*ptr)->callback == callback) |
break; |
old = *ptr; |
*ptr = old->next; |
kfree_s(old, sizeof(callback_list_t)); |
} |
|
static int queue_empty(struct apm_bios_struct * as) |
{ |
return as->event_head == as->event_tail; |
} |
|
static apm_event_t get_queued_event(struct apm_bios_struct * as) |
{ |
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS; |
return as->events[as->event_tail]; |
} |
|
static int queue_event(apm_event_t event, struct apm_bios_struct *sender) |
{ |
struct apm_bios_struct * as; |
|
if (user_list == NULL) |
return 0; |
for (as = user_list; as != NULL; as = as->next) { |
if (as == sender) |
continue; |
as->event_head = (as->event_head + 1) % APM_MAX_EVENTS; |
if (as->event_head == as->event_tail) { |
static int notified; |
|
if (notified == 0) { |
printk( "apm_bios: an event queue overflowed\n" ); |
notified = 1; |
} |
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS; |
} |
as->events[as->event_head] = event; |
if (!as->suser) |
continue; |
switch (event) { |
case APM_SYS_SUSPEND: |
case APM_USER_SUSPEND: |
as->suspends_pending++; |
suspends_pending++; |
break; |
|
case APM_SYS_STANDBY: |
case APM_USER_STANDBY: |
as->standbys_pending++; |
standbys_pending++; |
break; |
} |
} |
wake_up_interruptible(&process_list); |
return 1; |
} |
|
static void set_time(void) |
{ |
unsigned long flags; |
|
if (!got_clock_diff) /* Don't know time zone, can't set clock */ |
return; |
|
save_flags(flags); |
cli(); |
CURRENT_TIME = get_cmos_time() + clock_cmos_diff; |
restore_flags(flags); |
} |
|
static void suspend(void) |
{ |
unsigned long flags; |
int err; |
|
/* Estimate time zone so that set_time can |
update the clock */ |
save_flags(flags); |
clock_cmos_diff = -get_cmos_time(); |
cli(); |
clock_cmos_diff += CURRENT_TIME; |
got_clock_diff = 1; |
restore_flags(flags); |
|
err = apm_set_power_state(APM_STATE_SUSPEND); |
if (err) |
apm_error("suspend", err); |
set_time(); |
} |
|
static void standby(void) |
{ |
int err; |
|
err = apm_set_power_state(APM_STATE_STANDBY); |
if (err) |
apm_error("standby", err); |
} |
|
static apm_event_t get_event(void) |
{ |
int error; |
apm_event_t event; |
|
static int notified = 0; |
|
error = apm_get_event(&event); |
if (error == APM_SUCCESS) |
return event; |
|
if ((error != APM_NO_EVENTS) && (notified++ == 0)) |
apm_error("get_event", error); |
|
return 0; |
} |
|
static void send_event(apm_event_t event, apm_event_t undo, |
struct apm_bios_struct *sender) |
{ |
callback_list_t * call; |
callback_list_t * fix; |
|
for (call = callback_list; call != NULL; call = call->next) { |
if (call->callback(event) && undo) { |
for (fix = callback_list; fix != call; fix = fix->next) |
fix->callback(undo); |
if (apm_bios_info.version > 0x100) |
apm_set_power_state(APM_STATE_REJECT); |
return; |
} |
} |
|
queue_event(event, sender); |
} |
|
static void check_events(void) |
{ |
apm_event_t event; |
|
while ((event = get_event()) != 0) { |
#ifdef APM_DEBUG |
if (event <= NR_APM_EVENT_NAME) |
printk("APM BIOS received %s notify\n", |
apm_event_name[event - 1]); |
else |
printk("APM BIOS received unknown event 0x%02x\n", |
event); |
#endif |
switch (event) { |
case APM_SYS_STANDBY: |
case APM_USER_STANDBY: |
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND |
if (waiting_for_resume) { |
return; |
} |
waiting_for_resume = 1; |
#endif |
send_event(event, APM_STANDBY_RESUME, NULL); |
if (standbys_pending <= 0) |
standby(); |
break; |
|
case APM_USER_SUSPEND: |
#ifdef CONFIG_APM_IGNORE_USER_SUSPEND |
if (apm_bios_info.version > 0x100) |
apm_set_power_state(APM_STATE_REJECT); |
break; |
#endif |
case APM_SYS_SUSPEND: |
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND |
if (waiting_for_resume) { |
return; |
} |
waiting_for_resume = 1; |
#endif |
send_event(event, APM_NORMAL_RESUME, NULL); |
if (suspends_pending <= 0) |
suspend(); |
break; |
|
case APM_NORMAL_RESUME: |
case APM_CRITICAL_RESUME: |
case APM_STANDBY_RESUME: |
#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND |
waiting_for_resume = 0; |
#endif |
set_time(); |
send_event(event, 0, NULL); |
break; |
|
case APM_LOW_BATTERY: |
case APM_POWER_STATUS_CHANGE: |
send_event(event, 0, NULL); |
break; |
|
case APM_UPDATE_TIME: |
set_time(); |
break; |
|
case APM_CRITICAL_SUSPEND: |
suspend(); |
break; |
} |
} |
} |
|
static void do_apm_timer(unsigned long unused) |
{ |
int err; |
|
static int pending_count = 0; |
|
if (((standbys_pending > 0) || (suspends_pending > 0)) |
&& (apm_bios_info.version > 0x100) |
&& (pending_count-- <= 0)) { |
pending_count = 4; |
|
err = apm_set_power_state(APM_STATE_BUSY); |
if (err) |
apm_error("busy", err); |
} |
|
if (!(((standbys_pending > 0) || (suspends_pending > 0)) |
&& (apm_bios_info.version == 0x100))) |
check_events(); |
|
init_timer(&apm_timer); |
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies; |
add_timer(&apm_timer); |
} |
|
/* Called from sys_idle, must make sure apm_enabled. */ |
int apm_do_idle(void) |
{ |
#ifdef CONFIG_APM_CPU_IDLE |
unsigned short error; |
|
if (!apm_enabled) |
return 0; |
|
APM_SET_CPU_IDLE(error); |
if (error & 0xff) |
return 0; |
|
clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0; |
return 1; |
#else |
return 0; |
#endif |
} |
|
/* Called from sys_idle, must make sure apm_enabled. */ |
void apm_do_busy(void) |
{ |
#ifdef CONFIG_APM_CPU_IDLE |
unsigned short error; |
|
if (!apm_enabled) |
return; |
|
#ifndef ALWAYS_CALL_BUSY |
if (!clock_slowed) |
return; |
#endif |
|
APM_SET_CPU_BUSY(error); |
|
clock_slowed = 0; |
#endif |
} |
|
static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func) |
{ |
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) { |
printk("apm_bios: %s passed bad filp", func); |
return 1; |
} |
return 0; |
} |
|
static int do_read(struct inode *inode, struct file *fp, char *buf, int count) |
{ |
struct apm_bios_struct * as; |
int i; |
apm_event_t event; |
struct wait_queue wait = { current, NULL }; |
|
as = fp->private_data; |
if (check_apm_bios_struct(as, "read")) |
return -EIO; |
if (count < sizeof(apm_event_t)) |
return -EINVAL; |
if (queue_empty(as)) { |
if (fp->f_flags & O_NONBLOCK) |
return -EAGAIN; |
add_wait_queue(&process_list, &wait); |
repeat: |
current->state = TASK_INTERRUPTIBLE; |
if (queue_empty(as) |
&& !(current->signal & ~current->blocked)) { |
schedule(); |
goto repeat; |
} |
current->state = TASK_RUNNING; |
remove_wait_queue(&process_list, &wait); |
} |
i = count; |
while ((i >= sizeof(event)) && !queue_empty(as)) { |
event = get_queued_event(as); |
memcpy_tofs(buf, &event, sizeof(event)); |
switch (event) { |
case APM_SYS_SUSPEND: |
case APM_USER_SUSPEND: |
as->suspends_read++; |
break; |
|
case APM_SYS_STANDBY: |
case APM_USER_STANDBY: |
as->standbys_read++; |
break; |
} |
buf += sizeof(event); |
i -= sizeof(event); |
} |
if (i < count) |
return count - i; |
if (current->signal & ~current->blocked) |
return -ERESTARTSYS; |
return 0; |
} |
|
static int do_select(struct inode *inode, struct file *fp, int sel_type, |
select_table * wait) |
{ |
struct apm_bios_struct * as; |
|
as = fp->private_data; |
if (check_apm_bios_struct(as, "select")) |
return 0; |
if (sel_type != SEL_IN) |
return 0; |
if (!queue_empty(as)) |
return 1; |
select_wait(&process_list, wait); |
return 0; |
} |
|
static int do_ioctl(struct inode * inode, struct file *filp, |
u_int cmd, u_long arg) |
{ |
struct apm_bios_struct * as; |
|
as = filp->private_data; |
if (check_apm_bios_struct(as, "ioctl")) |
return -EIO; |
if (!as->suser) |
return -EPERM; |
switch (cmd) { |
case APM_IOC_STANDBY: |
if (as->standbys_read > 0) { |
as->standbys_read--; |
as->standbys_pending--; |
standbys_pending--; |
} |
else |
send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as); |
if (standbys_pending <= 0) |
standby(); |
break; |
case APM_IOC_SUSPEND: |
if (as->suspends_read > 0) { |
as->suspends_read--; |
as->suspends_pending--; |
suspends_pending--; |
} |
else |
send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as); |
if (suspends_pending <= 0) |
suspend(); |
break; |
default: |
return -EINVAL; |
} |
return 0; |
} |
|
static void do_release(struct inode * inode, struct file * filp) |
{ |
struct apm_bios_struct * as; |
|
as = filp->private_data; |
filp->private_data = NULL; |
if (check_apm_bios_struct(as, "release")) |
return; |
if (as->standbys_pending > 0) { |
standbys_pending -= as->standbys_pending; |
if (standbys_pending <= 0) |
standby(); |
} |
if (as->suspends_pending > 0) { |
suspends_pending -= as->suspends_pending; |
if (suspends_pending <= 0) |
suspend(); |
} |
if (user_list == as) |
user_list = as->next; |
else { |
struct apm_bios_struct * as1; |
|
for (as1 = user_list; |
(as1 != NULL) && (as1->next != as); |
as1 = as1->next) |
; |
if (as1 == NULL) |
printk("apm_bios: filp not in user list"); |
else |
as1->next = as->next; |
} |
kfree_s(as, sizeof(*as)); |
} |
|
static int do_open(struct inode * inode, struct file * filp) |
{ |
struct apm_bios_struct * as; |
|
as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL); |
if (as == NULL) { |
printk("apm_bios: cannot allocate struct of size %d bytes", |
sizeof(*as)); |
return -ENOMEM; |
} |
as->magic = APM_BIOS_MAGIC; |
as->event_tail = as->event_head = 0; |
as->suspends_pending = as->standbys_pending = 0; |
as->suspends_read = as->standbys_read = 0; |
as->suser = suser(); |
as->next = user_list; |
user_list = as; |
filp->private_data = as; |
return 0; |
} |
|
#ifdef CONFIG_PROC_FS |
int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy) |
{ |
char * p; |
unsigned short bx; |
unsigned short cx; |
unsigned short dx; |
unsigned short error; |
unsigned short ac_line_status = 0xff; |
unsigned short battery_status = 0xff; |
unsigned short battery_flag = 0xff; |
int percentage = -1; |
int time_units = -1; |
char *units = "?"; |
|
if (!apm_enabled) |
return 0; |
p = buf; |
|
if (!(error = apm_get_power_status(&bx, &cx, &dx))) { |
ac_line_status = (bx >> 8) & 0xff; |
battery_status = bx & 0xff; |
if ((cx & 0xff) != 0xff) |
percentage = cx & 0xff; |
|
if (apm_bios_info.version > 0x100) { |
battery_flag = (cx >> 8) & 0xff; |
if (dx != 0xffff) { |
if ((dx & 0x8000) == 0x8000) { |
units = "min"; |
time_units = dx & 0x7ffe; |
} else { |
units = "sec"; |
time_units = dx & 0x7fff; |
} |
} |
} |
} |
/* Arguments, with symbols from linux/apm_bios.h. Information is |
from the Get Power Status (0x0a) call unless otherwise noted. |
|
0) Linux driver version (this will change if format changes) |
1) APM BIOS Version. Usually 1.0 or 1.1. |
2) APM flags from APM Installation Check (0x00): |
bit 0: APM_16_BIT_SUPPORT |
bit 1: APM_32_BIT_SUPPORT |
bit 2: APM_IDLE_SLOWS_CLOCK |
bit 3: APM_BIOS_DISABLED |
bit 4: APM_BIOS_DISENGAGED |
3) AC line status |
0x00: Off-line |
0x01: On-line |
0x02: On backup power (APM BIOS 1.1 only) |
0xff: Unknown |
4) Battery status |
0x00: High |
0x01: Low |
0x02: Critical |
0x03: Charging |
0xff: Unknown |
5) Battery flag |
bit 0: High |
bit 1: Low |
bit 2: Critical |
bit 3: Charging |
bit 7: No system battery |
0xff: Unknown |
6) Remaining battery life (percentage of charge): |
0-100: valid |
-1: Unknown |
7) Remaining battery life (time units): |
Number of remaining minutes or seconds |
-1: Unknown |
8) min = minutes; sec = seconds */ |
|
p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n", |
driver_version, |
(apm_bios_info.version >> 8) & 0xff, |
apm_bios_info.version & 0xff, |
apm_bios_info.flags, |
ac_line_status, |
battery_status, |
battery_flag, |
percentage, |
time_units, |
units); |
|
return p - buf; |
} |
#endif |
|
static int apm_disabled = 0; |
|
void apm_setup(char *str, int *ints) |
{ |
if(strcmp(str,"off")==0) |
apm_disabled=1; |
if(strcmp(str,"on")==0) |
apm_disabled=0; |
} |
|
void apm_bios_init(void) |
{ |
unsigned short bx; |
unsigned short cx; |
unsigned short dx; |
unsigned short error; |
char * power_stat; |
char * bat_stat; |
|
if (apm_disabled == 1) |
{ |
printk("APM disabled.\n"); |
return; |
} |
|
if (apm_bios_info.version == 0) { |
printk("APM BIOS not found.\n"); |
return; |
} |
printk("APM BIOS version %c.%c Flags 0x%02x (Driver version %s)\n", |
((apm_bios_info.version >> 8) & 0xff) + '0', |
(apm_bios_info.version & 0xff) + '0', |
apm_bios_info.flags, |
driver_version); |
if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) { |
printk(" No 32 bit BIOS support\n"); |
return; |
} |
|
/* |
* Fix for the Compaq Contura 3/25c which reports BIOS version 0.1 |
* but is reportedly a 1.0 BIOS. |
*/ |
if (apm_bios_info.version == 0x001) |
apm_bios_info.version = 0x100; |
|
printk(" Entry %x:%lx cseg16 %x dseg %x", |
apm_bios_info.cseg, apm_bios_info.offset, |
apm_bios_info.cseg_16, apm_bios_info.dseg); |
if (apm_bios_info.version > 0x100) |
printk(" cseg len %x, dseg len %x", |
apm_bios_info.cseg_len, apm_bios_info.dseg_len); |
printk("\n"); |
|
apm_bios_entry.offset = apm_bios_info.offset; |
apm_bios_entry.segment = APM_CS; |
set_base(gdt[APM_CS >> 3], |
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg << 4)); |
set_base(gdt[APM_CS_16 >> 3], |
__PAGE_OFFSET + ((unsigned long)apm_bios_info.cseg_16 << 4)); |
set_base(gdt[APM_DS >> 3], |
__PAGE_OFFSET + ((unsigned long)apm_bios_info.dseg << 4)); |
if (apm_bios_info.version == 0x100) { |
set_limit(gdt[APM_CS >> 3], 64 * 1024); |
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024); |
set_limit(gdt[APM_DS >> 3], 64 * 1024); |
} else { |
#ifdef APM_RELAX_SEGMENTS |
/* For ASUS motherboard, Award BIOS rev 110 (and others?) */ |
set_limit(gdt[APM_CS >> 3], 64 * 1024); |
/* For some unknown machine. */ |
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024); |
/* For the DEC Hinote Ultra CT475 (and others?) */ |
set_limit(gdt[APM_DS >> 3], 64 * 1024); |
#else |
set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len); |
set_limit(gdt[APM_CS_16 >> 3], 64 * 1024); |
set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len); |
#endif |
apm_bios_info.version = 0x0101; |
error = apm_driver_version(&apm_bios_info.version); |
if (error != 0) |
apm_bios_info.version = 0x100; |
else { |
apm_engage_power_management(0x0001); |
printk( " Connection version %d.%d\n", |
(apm_bios_info.version >> 8) & 0xff, |
apm_bios_info.version & 0xff ); |
apm_bios_info.version = 0x0101; |
} |
} |
|
error = apm_get_power_status(&bx, &cx, &dx); |
if (error) |
printk(" Power status not available\n"); |
else { |
switch ((bx >> 8) & 0xff) { |
case 0: power_stat = "off line"; break; |
case 1: power_stat = "on line"; break; |
case 2: power_stat = "on backup power"; break; |
default: power_stat = "unknown"; break; |
} |
switch (bx & 0xff) { |
case 0: bat_stat = "high"; break; |
case 1: bat_stat = "low"; break; |
case 2: bat_stat = "critical"; break; |
case 3: bat_stat = "charging"; break; |
default: bat_stat = "unknown"; break; |
} |
printk(" AC %s, battery status %s, battery life ", |
power_stat, bat_stat); |
if ((cx & 0xff) == 0xff) |
printk("unknown\n"); |
else |
printk("%d%%\n", cx & 0xff); |
if (apm_bios_info.version > 0x100) { |
printk(" battery flag 0x%02x, battery life ", |
(cx >> 8) & 0xff); |
if (dx == 0xffff) |
printk("unknown\n"); |
else { |
if ((dx & 0x8000)) |
printk("%d minutes\n", dx & 0x7ffe ); |
else |
printk("%d seconds\n", dx & 0x7fff ); |
} |
} |
} |
|
#ifdef CONFIG_APM_DO_ENABLE |
/* |
* This call causes my NEC UltraLite Versa 33/C to hang if it is |
* booted with PM disabled but not in the docking station. |
* Unfortunate ... |
*/ |
error = apm_enable_power_management(); |
if (error) |
apm_error("enable power management", error); |
if (error == APM_DISABLED) |
return; |
#endif |
|
init_timer(&apm_timer); |
apm_timer.function = do_apm_timer; |
apm_timer.expires = APM_CHECK_TIMEOUT + jiffies; |
add_timer(&apm_timer); |
|
register_symtab(&apm_syms); |
|
#ifdef CONFIG_PROC_FS |
proc_register_dynamic(&proc_root, &apm_proc_entry); |
#endif |
|
misc_register(&apm_device); |
|
apm_enabled = 1; |
} |
/signal.c
0,0 → 1,359
/* |
* linux/arch/i386/kernel/signal.c |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
*/ |
|
#include <linux/config.h> |
|
#include <linux/sched.h> |
#include <linux/mm.h> |
#include <linux/kernel.h> |
#include <linux/signal.h> |
#include <linux/errno.h> |
#include <linux/wait.h> |
#include <linux/ptrace.h> |
#include <linux/unistd.h> |
|
#include <asm/segment.h> |
|
#define _S(nr) (1<<((nr)-1)) |
|
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) |
|
asmlinkage int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options); |
asmlinkage int do_signal(unsigned long oldmask, struct pt_regs * regs); |
|
/* |
* atomically swap in the new signal mask, and wait for a signal. |
*/ |
asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, unsigned long set) |
{ |
unsigned long mask; |
struct pt_regs * regs = (struct pt_regs *) &restart; |
|
mask = current->blocked; |
current->blocked = set & _BLOCKABLE; |
regs->eax = -EINTR; |
while (1) { |
current->state = TASK_INTERRUPTIBLE; |
schedule(); |
if (do_signal(mask,regs)) |
return -EINTR; |
} |
} |
|
static inline void restore_i387_hard(struct _fpstate *buf) |
{ |
#ifdef __SMP__ |
if (current->flags & PF_USEDFPU) { |
stts(); |
} |
#else |
if (current == last_task_used_math) { |
last_task_used_math = NULL; |
stts(); |
} |
#endif |
current->used_math = 1; |
current->flags &= ~PF_USEDFPU; |
memcpy_fromfs(¤t->tss.i387.hard, buf, sizeof(*buf)); |
} |
|
static void restore_i387(struct _fpstate *buf) |
{ |
#ifndef CONFIG_MATH_EMULATION |
restore_i387_hard(buf); |
#else |
if (hard_math) { |
restore_i387_hard(buf); |
return; |
} |
restore_i387_soft(buf); |
#endif |
} |
|
|
/* |
* This sets regs->esp even though we don't actually use sigstacks yet.. |
*/ |
asmlinkage int sys_sigreturn(unsigned long __unused) |
{ |
#define COPY(x) regs->x = context.x |
#define COPY_SEG(x) \ |
if ( (context.x & 0xfffc) /* not a NULL selectors */ \ |
&& (context.x & 0x4) != 0x4 /* not a LDT selector */ \ |
&& (context.x & 3) != 3 /* not a RPL3 GDT selector */ \ |
) goto badframe; COPY(x); |
#define COPY_SEG_STRICT(x) \ |
if (!(context.x & 0xfffc) || (context.x & 3) != 3) goto badframe; COPY(x); |
struct sigcontext_struct context; |
struct pt_regs * regs; |
|
regs = (struct pt_regs *) &__unused; |
if (verify_area(VERIFY_READ, (void *) regs->esp, sizeof(context))) |
goto badframe; |
memcpy_fromfs(&context,(void *) regs->esp, sizeof(context)); |
current->blocked = context.oldmask & _BLOCKABLE; |
COPY_SEG(ds); |
COPY_SEG(es); |
COPY_SEG(fs); |
COPY_SEG(gs); |
COPY_SEG_STRICT(ss); |
COPY_SEG_STRICT(cs); |
COPY(eip); |
COPY(ecx); COPY(edx); |
COPY(ebx); |
COPY(esp); COPY(ebp); |
COPY(edi); COPY(esi); |
regs->eflags &= ~0x40DD5; |
regs->eflags |= context.eflags & 0x40DD5; |
regs->orig_eax = -1; /* disable syscall checks */ |
if (context.fpstate) { |
struct _fpstate * buf = context.fpstate; |
if (verify_area(VERIFY_READ, buf, sizeof(*buf))) |
goto badframe; |
restore_i387(buf); |
} |
return context.eax; |
badframe: |
do_exit(SIGSEGV); |
} |
|
static inline struct _fpstate * save_i387_hard(struct _fpstate * buf) |
{ |
#ifdef __SMP__ |
if (current->flags & PF_USEDFPU) { |
__asm__ __volatile__("fnsave %0":"=m" (current->tss.i387.hard)); |
stts(); |
current->flags &= ~PF_USEDFPU; |
} |
#else |
if (current == last_task_used_math) { |
__asm__ __volatile__("fnsave %0":"=m" (current->tss.i387.hard)); |
last_task_used_math = NULL; |
__asm__ __volatile__("fwait"); /* not needed on 486+ */ |
stts(); |
} |
#endif |
current->tss.i387.hard.status = current->tss.i387.hard.swd; |
memcpy_tofs(buf, ¤t->tss.i387.hard, sizeof(*buf)); |
current->used_math = 0; |
return buf; |
} |
|
static struct _fpstate * save_i387(struct _fpstate * buf) |
{ |
if (!current->used_math) |
return NULL; |
|
#ifndef CONFIG_MATH_EMULATION |
return save_i387_hard(buf); |
#else |
if (hard_math) |
return save_i387_hard(buf); |
return save_i387_soft(buf); |
#endif |
} |
|
/* |
* Set up a signal frame... Make the stack look the way iBCS2 expects |
* it to look. |
*/ |
static void setup_frame(struct sigaction * sa, |
struct pt_regs * regs, int signr, |
unsigned long oldmask) |
{ |
unsigned long * frame; |
|
frame = (unsigned long *) regs->esp; |
if (regs->ss != USER_DS && sa->sa_restorer) |
frame = (unsigned long *) sa->sa_restorer; |
frame -= 64; |
if (verify_area(VERIFY_WRITE,frame,64*4)) |
do_exit(SIGSEGV); |
|
/* set up the "normal" stack seen by the signal handler (iBCS2) */ |
#define __CODE ((unsigned long)(frame+24)) |
#define CODE(x) ((unsigned long *) ((x)+__CODE)) |
put_user(__CODE,frame); |
if (current->exec_domain && current->exec_domain->signal_invmap) |
put_user(current->exec_domain->signal_invmap[signr], frame+1); |
else |
put_user(signr, frame+1); |
put_user(regs->gs, frame+2); |
put_user(regs->fs, frame+3); |
put_user(regs->es, frame+4); |
put_user(regs->ds, frame+5); |
put_user(regs->edi, frame+6); |
put_user(regs->esi, frame+7); |
put_user(regs->ebp, frame+8); |
put_user(regs->esp, frame+9); |
put_user(regs->ebx, frame+10); |
put_user(regs->edx, frame+11); |
put_user(regs->ecx, frame+12); |
put_user(regs->eax, frame+13); |
put_user(current->tss.trap_no, frame+14); |
put_user(current->tss.error_code, frame+15); |
put_user(regs->eip, frame+16); |
put_user(regs->cs, frame+17); |
put_user(regs->eflags, frame+18); |
put_user(regs->esp, frame+19); |
put_user(regs->ss, frame+20); |
put_user(save_i387((struct _fpstate *)(frame+32)),frame+21); |
/* non-iBCS2 extensions.. */ |
put_user(oldmask, frame+22); |
put_user(current->tss.cr2, frame+23); |
/* set up the return code... */ |
put_user(0x0000b858, CODE(0)); /* popl %eax ; movl $,%eax */ |
put_user(0x80cd0000, CODE(4)); /* int $0x80 */ |
put_user(__NR_sigreturn, CODE(2)); |
#undef __CODE |
#undef CODE |
|
/* Set up registers for signal handler */ |
regs->esp = (unsigned long) frame; |
regs->eip = (unsigned long) sa->sa_handler; |
regs->cs = USER_CS; regs->ss = USER_DS; |
regs->ds = USER_DS; regs->es = USER_DS; |
regs->gs = USER_DS; regs->fs = USER_DS; |
regs->eflags &= ~TF_MASK; |
} |
|
/* |
* OK, we're invoking a handler |
*/ |
static void handle_signal(unsigned long signr, struct sigaction *sa, |
unsigned long oldmask, struct pt_regs * regs) |
{ |
/* are we from a system call? */ |
if (regs->orig_eax >= 0) { |
/* If so, check system call restarting.. */ |
switch (regs->eax) { |
case -ERESTARTNOHAND: |
regs->eax = -EINTR; |
break; |
|
case -ERESTARTSYS: |
if (!(sa->sa_flags & SA_RESTART)) { |
regs->eax = -EINTR; |
break; |
} |
/* fallthrough */ |
case -ERESTARTNOINTR: |
regs->eax = regs->orig_eax; |
regs->eip -= 2; |
} |
} |
|
/* set up the stack frame */ |
setup_frame(sa, regs, signr, oldmask); |
|
if (sa->sa_flags & SA_ONESHOT) |
sa->sa_handler = NULL; |
if (!(sa->sa_flags & SA_NOMASK)) |
current->blocked |= (sa->sa_mask | _S(signr)) & _BLOCKABLE; |
} |
|
/* |
* Note that 'init' is a special process: it doesn't get signals it doesn't |
* want to handle. Thus you cannot kill init even with a SIGKILL even by |
* mistake. |
* |
* Note that we go through the signals twice: once to check the signals that |
* the kernel can handle, and then we build all the user-level signal handling |
* stack-frames in one go after that. |
*/ |
asmlinkage int do_signal(unsigned long oldmask, struct pt_regs * regs) |
{ |
unsigned long mask = ~current->blocked; |
unsigned long signr; |
struct sigaction * sa; |
|
while ((signr = current->signal & mask)) { |
/* |
* This stops gcc flipping out. Otherwise the assembler |
* including volatiles for the inline function to get |
* current combined with this gets it confused. |
*/ |
struct task_struct *t=current; |
__asm__("bsf %3,%1\n\t" |
"btrl %1,%0" |
:"=m" (t->signal),"=r" (signr) |
:"0" (t->signal), "1" (signr)); |
sa = current->sig->action + signr; |
signr++; |
if ((current->flags & PF_PTRACED) && signr != SIGKILL) { |
current->exit_code = signr; |
current->state = TASK_STOPPED; |
notify_parent(current, SIGCHLD); |
schedule(); |
if (!(signr = current->exit_code)) |
continue; |
current->exit_code = 0; |
if (signr == SIGSTOP) |
continue; |
if (_S(signr) & current->blocked) { |
current->signal |= _S(signr); |
continue; |
} |
sa = current->sig->action + signr - 1; |
} |
if (sa->sa_handler == SIG_IGN) { |
if (signr != SIGCHLD) |
continue; |
/* check for SIGCHLD: it's special */ |
while (sys_waitpid(-1,NULL,WNOHANG) > 0) |
/* nothing */; |
continue; |
} |
if (sa->sa_handler == SIG_DFL) { |
if (current->pid == 1) |
continue; |
switch (signr) { |
case SIGCONT: case SIGCHLD: case SIGWINCH: |
continue; |
|
case SIGTSTP: case SIGTTIN: case SIGTTOU: |
if (is_orphaned_pgrp(current->pgrp)) |
continue; |
case SIGSTOP: |
if (current->flags & PF_PTRACED) |
continue; |
current->state = TASK_STOPPED; |
current->exit_code = signr; |
if (!(current->p_pptr->sig->action[SIGCHLD-1].sa_flags & |
SA_NOCLDSTOP)) |
notify_parent(current, SIGCHLD); |
schedule(); |
continue; |
|
case SIGQUIT: case SIGILL: case SIGTRAP: |
case SIGABRT: case SIGFPE: case SIGSEGV: |
if (current->binfmt && current->binfmt->core_dump) { |
if (current->binfmt->core_dump(signr, regs)) |
signr |= 0x80; |
} |
/* fall through */ |
default: |
current->signal |= _S(signr & 0x7f); |
current->flags |= PF_SIGNALED; |
do_exit(signr); |
} |
} |
handle_signal(signr, sa, oldmask, regs); |
return 1; |
} |
|
/* Did we come from a system call? */ |
if (regs->orig_eax >= 0) { |
/* Restart the system call - no handlers present */ |
if (regs->eax == -ERESTARTNOHAND || |
regs->eax == -ERESTARTSYS || |
regs->eax == -ERESTARTNOINTR) { |
regs->eax = regs->orig_eax; |
regs->eip -= 2; |
} |
} |
return 0; |
} |
/process.c
0,0 → 1,617
/* |
* linux/arch/i386/kernel/process.c |
* |
* Copyright (C) 1995 Linus Torvalds |
*/ |
|
/* |
* This file handles the architecture-dependent parts of process handling.. |
*/ |
|
#define __KERNEL_SYSCALLS__ |
#include <stdarg.h> |
|
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/mm.h> |
#include <linux/stddef.h> |
#include <linux/unistd.h> |
#include <linux/ptrace.h> |
#include <linux/malloc.h> |
#include <linux/ldt.h> |
#include <linux/user.h> |
#include <linux/a.out.h> |
#include <linux/interrupt.h> |
#include <linux/config.h> |
#include <linux/unistd.h> |
#include <linux/delay.h> |
|
#include <asm/segment.h> |
#include <asm/pgtable.h> |
#include <asm/system.h> |
#include <asm/io.h> |
#include <linux/smp.h> |
|
asmlinkage void ret_from_sys_call(void) __asm__("ret_from_sys_call"); |
|
#ifdef CONFIG_APM |
extern int apm_do_idle(void); |
extern void apm_do_busy(void); |
#endif |
|
static int hlt_counter=0; |
|
#define HARD_IDLE_TIMEOUT (HZ / 3) |
|
void disable_hlt(void) |
{ |
hlt_counter++; |
} |
|
void enable_hlt(void) |
{ |
hlt_counter--; |
} |
|
#ifndef __SMP__ |
|
static void hard_idle(void) |
{ |
while (!need_resched) { |
if (hlt_works_ok && !hlt_counter) { |
#ifdef CONFIG_APM |
/* If the APM BIOS is not enabled, or there |
is an error calling the idle routine, we |
should hlt if possible. We need to check |
need_resched again because an interrupt |
may have occurred in apm_do_idle(). */ |
start_bh_atomic(); |
if (!apm_do_idle() && !need_resched) |
__asm__("hlt"); |
end_bh_atomic(); |
#else |
__asm__("hlt"); |
#endif |
} |
if (need_resched) |
break; |
schedule(); |
} |
#ifdef CONFIG_APM |
apm_do_busy(); |
#endif |
} |
|
/* |
* The idle loop on a uniprocessor i386.. |
*/ |
|
asmlinkage int sys_idle(void) |
{ |
unsigned long start_idle = 0; |
|
if (current->pid != 0) |
return -EPERM; |
/* endless idle loop with no priority at all */ |
current->counter = -100; |
for (;;) |
{ |
/* |
* We are locked at this point. So we can safely call |
* the APM bios knowing only one CPU at a time will do |
* so. |
*/ |
if (!start_idle) |
start_idle = jiffies; |
if (jiffies - start_idle > HARD_IDLE_TIMEOUT) |
{ |
hard_idle(); |
} |
else |
{ |
if (hlt_works_ok && !hlt_counter && !need_resched) |
__asm__("hlt"); |
} |
if (need_resched) |
start_idle = 0; |
schedule(); |
} |
} |
|
#else |
|
/* |
* In the SMP world we hlt outside of kernel syscall rather than within |
* so as to get the right locking semantics. |
*/ |
|
asmlinkage int sys_idle(void) |
{ |
if(current->pid != 0) |
return -EPERM; |
#ifdef __SMP_PROF__ |
smp_spins_sys_idle[smp_processor_id()]+= |
smp_spins_syscall_cur[smp_processor_id()]; |
#endif |
current->counter= -100; |
schedule(); |
return 0; |
} |
|
/* |
* This is being executed in task 0 'user space'. |
*/ |
|
int cpu_idle(void *unused) |
{ |
while(1) |
{ |
if(cpu_data[smp_processor_id()].hlt_works_ok && !hlt_counter && !need_resched) |
__asm("hlt"); |
if(0==(0x7fffffff & smp_process_available)) |
continue; |
while(0x80000000 & smp_process_available); |
cli(); |
while(set_bit(31,&smp_process_available)) |
while(test_bit(31,&smp_process_available)) |
{ |
/* |
* Oops.. This is kind of important in some cases... |
*/ |
if(clear_bit(smp_processor_id(), &smp_invalidate_needed)) |
local_flush_tlb(); |
} |
if (0==(0x7fffffff & smp_process_available)){ |
clear_bit(31,&smp_process_available); |
sti(); |
continue; |
} |
smp_process_available--; |
clear_bit(31,&smp_process_available); |
sti(); |
idle(); |
} |
} |
|
#endif |
|
/* |
* This routine reboots the machine by asking the keyboard |
* controller to pulse the reset-line low. We try that for a while, |
* and if it doesn't work, we do some other stupid things. |
*/ |
static long no_idt[2] = {0, 0}; |
static int reboot_mode = 0; |
static int reboot_thru_bios = 0; |
|
void reboot_setup(char *str, int *ints) |
{ |
while(1) { |
switch (*str) { |
case 'w': /* "warm" reboot (no memory testing etc) */ |
reboot_mode = 0x1234; |
break; |
case 'c': /* "cold" reboot (with memory testing etc) */ |
reboot_mode = 0x0; |
break; |
case 'b': /* "bios" reboot by jumping through the BIOS */ |
reboot_thru_bios = 1; |
break; |
case 'h': /* "hard" reboot by toggling RESET and/or crashing the CPU */ |
reboot_thru_bios = 0; |
break; |
} |
if((str = strchr(str,',')) != NULL) |
str++; |
else |
break; |
} |
} |
|
|
/* The following code and data reboots the machine by switching to real |
mode and jumping to the BIOS reset entry point, as if the CPU has |
really been reset. The previous version asked the keyboard |
controller to pulse the CPU reset line, which is more thorough, but |
doesn't work with at least one type of 486 motherboard. It is easy |
to stop this code working; hence the copious comments. */ |
|
unsigned long long |
real_mode_gdt_entries [3] = |
{ |
0x0000000000000000ULL, /* Null descriptor */ |
0x00009a000000ffffULL, /* 16-bit real-mode 64k code at 0x00000000 */ |
0x000092000100ffffULL /* 16-bit real-mode 64k data at 0x00000100 */ |
}; |
|
struct |
{ |
unsigned short size __attribute__ ((packed)); |
unsigned long long * base __attribute__ ((packed)); |
} |
real_mode_gdt = { sizeof (real_mode_gdt_entries) - 1, real_mode_gdt_entries }, |
real_mode_idt = { 0x3ff, 0 }; |
|
/* This is 16-bit protected mode code to disable paging and the cache, |
switch to real mode and jump to the BIOS reset code. |
|
The instruction that switches to real mode by writing to CR0 must be |
followed immediately by a far jump instruction, which set CS to a |
valid value for real mode, and flushes the prefetch queue to avoid |
running instructions that have already been decoded in protected |
mode. |
|
Clears all the flags except ET, especially PG (paging), PE |
(protected-mode enable) and TS (task switch for coprocessor state |
save). Flushes the TLB after paging has been disabled. Sets CD and |
NW, to disable the cache on a 486, and invalidates the cache. This |
is more like the state of a 486 after reset. I don't know if |
something else should be done for other chips. |
|
More could be done here to set up the registers as if a CPU reset had |
occurred; hopefully real BIOSes don't assume much. */ |
|
unsigned char real_mode_switch [] = |
{ |
0x66, 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */ |
0x66, 0x83, 0xe0, 0x11, /* andl $0x00000011,%eax */ |
0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /* orl $0x60000000,%eax */ |
0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */ |
0x66, 0x0f, 0x22, 0xd8, /* movl %eax,%cr3 */ |
0x66, 0x0f, 0x20, 0xc3, /* movl %cr0,%ebx */ |
0x66, 0x81, 0xe3, 0x00, 0x00, 0x00, 0x60, /* andl $0x60000000,%ebx */ |
0x74, 0x02, /* jz f */ |
0x0f, 0x08, /* invd */ |
0x24, 0x10, /* f: andb $0x10,al */ |
0x66, 0x0f, 0x22, 0xc0, /* movl %eax,%cr0 */ |
0xea, 0x00, 0x00, 0xff, 0xff /* ljmp $0xffff,$0x0000 */ |
}; |
|
static inline void kb_wait(void) |
{ |
int i; |
for (i=0; i<0x10000; i++) |
if ((inb_p(0x64) & 0x02) == 0) |
break; |
} |
|
void hard_reset_now (void) |
{ |
|
if(!reboot_thru_bios) { |
sti(); |
/* rebooting needs to touch the page at absolute addr 0 */ |
pg0[0] = 7; |
*((unsigned short *)0x472) = reboot_mode; |
for (;;) { |
int i; |
for (i=0; i<100; i++) { |
int j; |
kb_wait(); |
for(j = 0; j < 100000 ; j++) |
/* nothing */; |
outb(0xfe,0x64); /* pulse reset low */ |
udelay(10); |
} |
__asm__ __volatile__("\tlidt %0": "=m" (no_idt)); |
} |
} |
|
cli (); |
|
/* Write zero to CMOS register number 0x0f, which the BIOS POST |
routine will recognize as telling it to do a proper reboot. (Well |
that's what this book in front of me says -- it may only apply to |
the Phoenix BIOS though, it's not clear). At the same time, |
disable NMIs by setting the top bit in the CMOS address register, |
as we're about to do peculiar things to the CPU. I'm not sure if |
`outb_p' is needed instead of just `outb'. Use it to be on the |
safe side. */ |
|
outb_p (0x8f, 0x70); |
outb_p (0x00, 0x71); |
|
/* Remap the kernel at virtual address zero, as well as offset zero |
from the kernel segment. This assumes the kernel segment starts at |
virtual address PAGE_OFFSET. */ |
|
memcpy (swapper_pg_dir, swapper_pg_dir + USER_PGD_PTRS, |
sizeof (swapper_pg_dir [0]) * KERNEL_PGD_PTRS); |
|
/* Make sure the first page is mapped to the start of physical memory. |
It is normally not mapped, to trap kernel NULL pointer dereferences. */ |
|
pg0 [0] = 7; |
|
/* Use `swapper_pg_dir' as our page directory. Don't bother with |
`SET_PAGE_DIR' because interrupts are disabled and we're rebooting. |
This instruction flushes the TLB. */ |
|
__asm__ __volatile__ ("movl %0,%%cr3" : : "a" (swapper_pg_dir) : "memory"); |
|
/* Write 0x1234 to absolute memory location 0x472. The BIOS reads |
this on booting to tell it to "Bypass memory test (also warm |
boot)". This seems like a fairly standard thing that gets set by |
REBOOT.COM programs, and the previous reset routine did this |
too. */ |
|
*((unsigned short *)0x472) = reboot_mode; |
|
/* For the switch to real mode, copy some code to low memory. It has |
to be in the first 64k because it is running in 16-bit mode, and it |
has to have the same physical and virtual address, because it turns |
off paging. Copy it near the end of the first page, out of the way |
of BIOS variables. */ |
|
memcpy ((void *) (0x1000 - sizeof (real_mode_switch)), |
real_mode_switch, sizeof (real_mode_switch)); |
|
/* Set up the IDT for real mode. */ |
|
__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt)); |
|
/* Set up a GDT from which we can load segment descriptors for real |
mode. The GDT is not used in real mode; it is just needed here to |
prepare the descriptors. */ |
|
__asm__ __volatile__ ("lgdt %0" : : "m" (real_mode_gdt)); |
|
/* Load the data segment registers, and thus the descriptors ready for |
real mode. The base address of each segment is 0x100, 16 times the |
selector value being loaded here. This is so that the segment |
registers don't have to be reloaded after switching to real mode: |
the values are consistent for real mode operation already. */ |
|
__asm__ __volatile__ ("movw $0x0010,%%ax\n" |
"\tmovw %%ax,%%ds\n" |
"\tmovw %%ax,%%es\n" |
"\tmovw %%ax,%%fs\n" |
"\tmovw %%ax,%%gs\n" |
"\tmovw %%ax,%%ss" : : : "eax"); |
|
/* Jump to the 16-bit code that we copied earlier. It disables paging |
and the cache, switches to real mode, and jumps to the BIOS reset |
entry point. */ |
|
__asm__ __volatile__ ("ljmp $0x0008,%0" |
: |
: "i" ((void *) (0x1000 - sizeof (real_mode_switch)))); |
} |
|
void show_regs(struct pt_regs * regs) |
{ |
printk("\n"); |
printk("EIP: %04x:[<%08lx>]",0xffff & regs->cs,regs->eip); |
if (regs->cs & 3) |
printk(" ESP: %04x:%08lx",0xffff & regs->ss,regs->esp); |
printk(" EFLAGS: %08lx\n",regs->eflags); |
printk("EAX: %08lx EBX: %08lx ECX: %08lx EDX: %08lx\n", |
regs->eax,regs->ebx,regs->ecx,regs->edx); |
printk("ESI: %08lx EDI: %08lx EBP: %08lx", |
regs->esi, regs->edi, regs->ebp); |
printk(" DS: %04x ES: %04x FS: %04x GS: %04x\n", |
0xffff & regs->ds,0xffff & regs->es, |
0xffff & regs->fs,0xffff & regs->gs); |
} |
|
/* |
* Free current thread data structures etc.. |
*/ |
|
void exit_thread(void) |
{ |
/* forget lazy i387 state */ |
if (last_task_used_math == current) |
last_task_used_math = NULL; |
/* forget local segments */ |
__asm__ __volatile__("mov %w0,%%fs ; mov %w0,%%gs ; lldt %w0" |
: /* no outputs */ |
: "r" (0)); |
current->tss.ldt = 0; |
if (current->ldt) { |
void * ldt = current->ldt; |
current->ldt = NULL; |
vfree(ldt); |
} |
} |
|
void flush_thread(void) |
{ |
int i; |
|
if (current->ldt) { |
void * ldt = current->ldt; |
current->ldt = NULL; |
vfree(ldt); |
for (i=1 ; i<NR_TASKS ; i++) { |
if (task[i] == current) { |
set_ldt_desc(gdt+(i<<1)+ |
FIRST_LDT_ENTRY,&default_ldt, 1); |
load_ldt(i); |
} |
} |
} |
|
for (i=0 ; i<8 ; i++) |
current->debugreg[i] = 0; |
|
/* |
* Forget coprocessor state.. |
*/ |
#ifdef __SMP__ |
if (current->flags & PF_USEDFPU) { |
stts(); |
} |
#else |
if (last_task_used_math == current) { |
last_task_used_math = NULL; |
stts(); |
} |
#endif |
current->used_math = 0; |
current->flags &= ~PF_USEDFPU; |
} |
|
void release_thread(struct task_struct *dead_task) |
{ |
} |
|
void copy_thread(int nr, unsigned long clone_flags, unsigned long esp, |
struct task_struct * p, struct pt_regs * regs) |
{ |
int i; |
struct pt_regs * childregs; |
|
p->tss.es = KERNEL_DS; |
p->tss.cs = KERNEL_CS; |
p->tss.ss = KERNEL_DS; |
p->tss.ds = KERNEL_DS; |
p->tss.fs = USER_DS; |
p->tss.gs = KERNEL_DS; |
p->tss.ss0 = KERNEL_DS; |
p->tss.esp0 = p->kernel_stack_page + PAGE_SIZE; |
p->tss.tr = _TSS(nr); |
childregs = ((struct pt_regs *) (p->kernel_stack_page + PAGE_SIZE)) - 1; |
p->tss.esp = (unsigned long) childregs; |
p->tss.eip = (unsigned long) ret_from_sys_call; |
*childregs = *regs; |
childregs->eax = 0; |
childregs->esp = esp; |
p->tss.back_link = 0; |
p->tss.eflags = regs->eflags & 0xffffcfff; /* iopl is always 0 for a new process */ |
p->tss.ldt = _LDT(nr); |
if (p->ldt) { |
p->ldt = (struct desc_struct*) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE); |
if (p->ldt != NULL) |
memcpy(p->ldt, current->ldt, LDT_ENTRIES*LDT_ENTRY_SIZE); |
} |
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); |
if (p->ldt) |
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,p->ldt, LDT_ENTRIES); |
else |
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&default_ldt, 1); |
p->tss.bitmap = offsetof(struct thread_struct,io_bitmap); |
for (i = 0; i < IO_BITMAP_SIZE+1 ; i++) /* IO bitmap is actually SIZE+1 */ |
p->tss.io_bitmap[i] = ~0; |
if (last_task_used_math == current) |
__asm__("clts ; fnsave %0 ; frstor %0":"=m" (p->tss.i387)); |
} |
|
/* |
* fill in the fpu structure for a core dump.. |
*/ |
int dump_fpu (struct pt_regs * regs, struct user_i387_struct* fpu) |
{ |
int fpvalid; |
|
if (hard_math) { |
if ((fpvalid = current->used_math) != 0) { |
#ifdef __SMP__ |
if (current->flags & PF_USEDFPU) |
#else |
if (last_task_used_math == current) |
#endif |
__asm__("clts ; fnsave %0": :"m" (*fpu)); |
else |
memcpy(fpu,¤t->tss.i387.hard,sizeof(*fpu)); |
} |
} else { |
/* We dump the emulator state here. |
We convert it into standard 387 format first.. */ |
#ifdef CONFIG_MATH_EMULATION |
int i; |
unsigned long top; |
char (*hardreg)[10]; |
struct i387_soft_struct *soft_fpu = ¤t->tss.i387.soft; |
struct fpu_reg* softreg; |
long int control_word = soft_fpu->cwd; |
|
fpu->cwd = soft_fpu->cwd; |
fpu->swd = soft_fpu->swd; |
fpu->twd = soft_fpu->twd; |
fpu->fip = soft_fpu->fip; |
fpu->fcs = soft_fpu->fcs; |
fpu->foo = soft_fpu->foo; |
fpu->fos = soft_fpu->fos; |
hardreg = (char (*)[10]) &fpu->st_space[0]; |
top = (unsigned long) soft_fpu->top % 8; |
softreg = &soft_fpu->regs[top]; |
for (i = top ; i < 8; i ++) { |
softreg_to_hardreg(softreg, *hardreg, control_word); |
hardreg++; |
softreg++; |
} |
softreg = &soft_fpu->regs[0]; |
for (i = 0; i < top; i++) { |
softreg_to_hardreg(softreg, *hardreg, control_word); |
hardreg++; |
softreg++; |
} |
fpvalid = 1; |
#else /* defined(CONFIG_MATH_EMULATION) */ |
fpvalid = 0; |
#endif /* !defined(CONFIG_MATH_EMULATION) */ |
} |
|
return fpvalid; |
} |
|
/* |
* fill in the user structure for a core dump.. |
*/ |
void dump_thread(struct pt_regs * regs, struct user * dump) |
{ |
int i; |
|
/* changed the size calculations - should hopefully work better. lbt */ |
dump->magic = CMAGIC; |
dump->start_code = 0; |
dump->start_stack = regs->esp & ~(PAGE_SIZE - 1); |
dump->u_tsize = ((unsigned long) current->mm->end_code) >> PAGE_SHIFT; |
dump->u_dsize = ((unsigned long) (current->mm->brk + (PAGE_SIZE-1))) >> PAGE_SHIFT; |
dump->u_dsize -= dump->u_tsize; |
dump->u_ssize = 0; |
for (i = 0; i < 8; i++) |
dump->u_debugreg[i] = current->debugreg[i]; |
|
if (dump->start_stack < TASK_SIZE) |
dump->u_ssize = ((unsigned long) (TASK_SIZE - dump->start_stack)) >> PAGE_SHIFT; |
|
dump->regs = *regs; |
|
dump->u_fpvalid = dump_fpu (regs, &dump->i387); |
} |
|
asmlinkage int sys_fork(struct pt_regs regs) |
{ |
return do_fork(SIGCHLD, regs.esp, ®s); |
} |
|
asmlinkage int sys_clone(struct pt_regs regs) |
{ |
unsigned long clone_flags; |
unsigned long newsp; |
|
clone_flags = regs.ebx; |
newsp = regs.ecx; |
if (!newsp) |
newsp = regs.esp; |
return do_fork(clone_flags, newsp, ®s); |
} |
|
/* |
* sys_execve() executes a new program. |
*/ |
asmlinkage int sys_execve(struct pt_regs regs) |
{ |
int error; |
char * filename; |
|
error = getname((char *) regs.ebx, &filename); |
if (error) |
return error; |
error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s); |
putname(filename); |
return error; |
} |
/ptrace.c
0,0 → 1,740
/* ptrace.c */ |
/* By Ross Biro 1/23/92 */ |
/* edited by Linus Torvalds */ |
|
#include <linux/config.h> /* CONFIG_MATH_EMULATION */ |
#include <linux/head.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/mm.h> |
#include <linux/errno.h> |
#include <linux/ptrace.h> |
#include <linux/user.h> |
#include <linux/debugreg.h> |
|
#include <asm/segment.h> |
#include <asm/pgtable.h> |
#include <asm/system.h> |
|
/* |
* does not yet catch signals sent when the child dies. |
* in exit.c or in signal.c. |
*/ |
|
/* determines which flags the user has access to. */ |
/* 1 = access 0 = no access */ |
#define FLAG_MASK 0x00044dd5 |
|
/* set's the trap flag. */ |
#define TRAP_FLAG 0x100 |
|
/* |
* this is the number to subtract from the top of the stack. To find |
* the local frame. |
*/ |
#define MAGICNUMBER 68 |
|
/* change a pid into a task struct. */ |
static inline struct task_struct * get_task(int pid) |
{ |
int i; |
|
for (i = 1; i < NR_TASKS; i++) { |
if (task[i] != NULL && (task[i]->pid == pid)) |
return task[i]; |
} |
return NULL; |
} |
|
/* |
* this routine will get a word off of the processes privileged stack. |
* the offset is how far from the base addr as stored in the TSS. |
* this routine assumes that all the privileged stacks are in our |
* data space. |
*/ |
static inline int get_stack_long(struct task_struct *task, int offset) |
{ |
unsigned char *stack; |
|
stack = (unsigned char *)task->tss.esp0; |
stack += offset; |
return (*((int *)stack)); |
} |
|
/* |
* this routine will put a word on the processes privileged stack. |
* the offset is how far from the base addr as stored in the TSS. |
* this routine assumes that all the privileged stacks are in our |
* data space. |
*/ |
static inline int put_stack_long(struct task_struct *task, int offset, |
unsigned long data) |
{ |
unsigned char * stack; |
|
stack = (unsigned char *) task->tss.esp0; |
stack += offset; |
*(unsigned long *) stack = data; |
return 0; |
} |
|
/* |
* This routine gets a long from any process space by following the page |
* tables. NOTE! You should check that the long isn't on a page boundary, |
* and that it is in the task area before calling this: this routine does |
* no checking. |
*/ |
static unsigned long get_long(struct task_struct * tsk, |
struct vm_area_struct * vma, unsigned long addr) |
{ |
pgd_t * pgdir; |
pmd_t * pgmiddle; |
pte_t * pgtable; |
unsigned long page; |
|
repeat: |
pgdir = pgd_offset(vma->vm_mm, addr); |
if (pgd_none(*pgdir)) { |
do_no_page(tsk, vma, addr, 0); |
goto repeat; |
} |
if (pgd_bad(*pgdir)) { |
printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir)); |
pgd_clear(pgdir); |
return 0; |
} |
pgmiddle = pmd_offset(pgdir, addr); |
if (pmd_none(*pgmiddle)) { |
do_no_page(tsk, vma, addr, 0); |
goto repeat; |
} |
if (pmd_bad(*pgmiddle)) { |
printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle)); |
pmd_clear(pgmiddle); |
return 0; |
} |
pgtable = pte_offset(pgmiddle, addr); |
if (!pte_present(*pgtable)) { |
do_no_page(tsk, vma, addr, 0); |
goto repeat; |
} |
page = pte_page(*pgtable); |
/* this is a hack for non-kernel-mapped video buffers and similar */ |
if (page >= high_memory) |
return 0; |
page += addr & ~PAGE_MASK; |
return *(unsigned long *) page; |
} |
|
/* |
* This routine puts a long into any process space by following the page |
* tables. NOTE! You should check that the long isn't on a page boundary, |
* and that it is in the task area before calling this: this routine does |
* no checking. |
* |
* Now keeps R/W state of page so that a text page stays readonly |
* even if a debugger scribbles breakpoints into it. -M.U- |
*/ |
static void put_long(struct task_struct * tsk, struct vm_area_struct * vma, unsigned long addr, |
unsigned long data) |
{ |
pgd_t *pgdir; |
pmd_t *pgmiddle; |
pte_t *pgtable; |
unsigned long page; |
|
repeat: |
pgdir = pgd_offset(vma->vm_mm, addr); |
if (!pgd_present(*pgdir)) { |
do_no_page(tsk, vma, addr, 1); |
goto repeat; |
} |
if (pgd_bad(*pgdir)) { |
printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir)); |
pgd_clear(pgdir); |
return; |
} |
pgmiddle = pmd_offset(pgdir, addr); |
if (pmd_none(*pgmiddle)) { |
do_no_page(tsk, vma, addr, 1); |
goto repeat; |
} |
if (pmd_bad(*pgmiddle)) { |
printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle)); |
pmd_clear(pgmiddle); |
return; |
} |
pgtable = pte_offset(pgmiddle, addr); |
if (!pte_present(*pgtable)) { |
do_no_page(tsk, vma, addr, 1); |
goto repeat; |
} |
page = pte_page(*pgtable); |
if (!pte_write(*pgtable)) { |
do_wp_page(tsk, vma, addr, 1); |
goto repeat; |
} |
/* this is a hack for non-kernel-mapped video buffers and similar */ |
if (page < high_memory) |
*(unsigned long *) (page + (addr & ~PAGE_MASK)) = data; |
/* we're bypassing pagetables, so we have to set the dirty bit ourselves */ |
/* this should also re-instate whatever read-only mode there was before */ |
set_pte(pgtable, pte_mkdirty(mk_pte(page, vma->vm_page_prot))); |
flush_tlb(); |
} |
|
static struct vm_area_struct * find_extend_vma(struct task_struct * tsk, unsigned long addr) |
{ |
struct vm_area_struct * vma; |
|
addr &= PAGE_MASK; |
vma = find_vma(tsk->mm,addr); |
if (!vma) |
return NULL; |
if (vma->vm_start <= addr) |
return vma; |
if (!(vma->vm_flags & VM_GROWSDOWN)) |
return NULL; |
if (vma->vm_end - addr > tsk->rlim[RLIMIT_STACK].rlim_cur) |
return NULL; |
vma->vm_offset -= vma->vm_start - addr; |
vma->vm_start = addr; |
return vma; |
} |
|
/* |
* This routine checks the page boundaries, and that the offset is |
* within the task area. It then calls get_long() to read a long. |
*/ |
static int read_long(struct task_struct * tsk, unsigned long addr, |
unsigned long * result) |
{ |
struct vm_area_struct * vma = find_extend_vma(tsk, addr); |
|
if (!vma) |
return -EIO; |
if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) { |
unsigned long low,high; |
struct vm_area_struct * vma_high = vma; |
|
if (addr + sizeof(long) >= vma->vm_end) { |
vma_high = vma->vm_next; |
if (!vma_high || vma_high->vm_start != vma->vm_end) |
return -EIO; |
} |
low = get_long(tsk, vma, addr & ~(sizeof(long)-1)); |
high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1)); |
switch (addr & (sizeof(long)-1)) { |
case 1: |
low >>= 8; |
low |= high << 24; |
break; |
case 2: |
low >>= 16; |
low |= high << 16; |
break; |
case 3: |
low >>= 24; |
low |= high << 8; |
break; |
} |
*result = low; |
} else |
*result = get_long(tsk, vma, addr); |
return 0; |
} |
|
/* |
* This routine checks the page boundaries, and that the offset is |
* within the task area. It then calls put_long() to write a long. |
*/ |
static int write_long(struct task_struct * tsk, unsigned long addr, |
unsigned long data) |
{ |
struct vm_area_struct * vma = find_extend_vma(tsk, addr); |
|
if (!vma) |
return -EIO; |
if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) { |
unsigned long low,high; |
struct vm_area_struct * vma_high = vma; |
|
if (addr + sizeof(long) >= vma->vm_end) { |
vma_high = vma->vm_next; |
if (!vma_high || vma_high->vm_start != vma->vm_end) |
return -EIO; |
} |
low = get_long(tsk, vma, addr & ~(sizeof(long)-1)); |
high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1)); |
switch (addr & (sizeof(long)-1)) { |
case 0: /* shouldn't happen, but safety first */ |
low = data; |
break; |
case 1: |
low &= 0x000000ff; |
low |= data << 8; |
high &= ~0xff; |
high |= data >> 24; |
break; |
case 2: |
low &= 0x0000ffff; |
low |= data << 16; |
high &= ~0xffff; |
high |= data >> 16; |
break; |
case 3: |
low &= 0x00ffffff; |
low |= data << 24; |
high &= ~0xffffff; |
high |= data >> 8; |
break; |
} |
put_long(tsk, vma, addr & ~(sizeof(long)-1),low); |
put_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1),high); |
} else |
put_long(tsk, vma, addr, data); |
return 0; |
} |
#ifdef CONFIG_MATH_EMULATION |
static void write_emulator_word(struct task_struct *child, |
unsigned long register_offset, |
long data) |
{ |
int i, j; |
struct i387_soft_struct *soft_fpu; |
struct fpu_reg *this_fpreg, *next_fpreg; |
char hard_reg[2][10]; |
int control_word; |
unsigned long top; |
i = register_offset / 10; |
j = register_offset % 10; |
soft_fpu = &child->tss.i387.soft; |
top = i + (unsigned long) soft_fpu->top; |
control_word = soft_fpu->cwd; |
this_fpreg = &soft_fpu->regs[(top + i) % 8]; |
next_fpreg = &soft_fpu->regs[(top + i + 1) % 8]; |
softreg_to_hardreg(this_fpreg, hard_reg[0], control_word); |
if (j > 6) |
softreg_to_hardreg(next_fpreg, hard_reg[1], control_word); |
*(long *) &hard_reg[0][j] = data; |
hardreg_to_softreg(hard_reg[0], this_fpreg); |
if (j > 6) |
hardreg_to_softreg(hard_reg[1], next_fpreg); |
} |
#endif /* defined(CONFIG_MATH_EMULATION) */ |
|
/* |
* Floating point support added to ptrace by Ramon Garcia, |
* ramon@juguete.quim.ucm.es |
*/ |
|
#ifdef CONFIG_MATH_EMULATION |
|
static unsigned long get_emulator_word(struct task_struct *child, |
unsigned long register_offset) |
{ |
char hard_reg[2][10]; |
int i, j; |
struct fpu_reg *this_fpreg, *next_fpreg; |
struct i387_soft_struct *soft_fpu; |
long int control_word; |
unsigned long top; |
unsigned long tmp; |
i = register_offset / 10; |
j = register_offset % 10; |
soft_fpu = &child->tss.i387.soft; |
top = (unsigned long) soft_fpu->top; |
this_fpreg = &soft_fpu->regs[(top + i) % 8]; |
next_fpreg = &soft_fpu->regs[(top + i + 1) % 8]; |
control_word = soft_fpu->cwd; |
softreg_to_hardreg(this_fpreg, hard_reg[0], control_word); |
if (j > 6) |
softreg_to_hardreg(next_fpreg, hard_reg[1], control_word); |
tmp = *(long *) |
&hard_reg[0][j]; |
return tmp; |
} |
|
#endif /* defined(CONFIG_MATH_EMULATION) */ |
|
static int putreg(struct task_struct *child, |
unsigned long regno, unsigned long value) |
{ |
switch (regno >> 2) { |
case ORIG_EAX: |
return -EIO; |
case FS: |
case GS: |
case DS: |
case ES: |
if (value && (value & 3) != 3) |
return -EIO; |
value &= 0xffff; |
break; |
case SS: |
case CS: |
if ((value & 3) != 3) |
return -EIO; |
value &= 0xffff; |
break; |
case EFL: |
value &= FLAG_MASK; |
value |= get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~FLAG_MASK; |
} |
put_stack_long(child, regno - sizeof(struct pt_regs), value); |
return 0; |
} |
|
static unsigned long getreg(struct task_struct *child, |
unsigned long regno) |
{ |
unsigned long retval = ~0UL; |
|
switch (regno >> 2) { |
case FS: |
case GS: |
case DS: |
case ES: |
case SS: |
case CS: |
retval = 0xffff; |
/* fall through */ |
default: |
regno = regno - sizeof(struct pt_regs); |
retval &= get_stack_long(child, regno); |
} |
return retval; |
} |
|
asmlinkage int sys_ptrace(long request, long pid, long addr, long data) |
{ |
struct task_struct *child; |
struct user * dummy; |
int i; |
|
dummy = NULL; |
|
if (request == PTRACE_TRACEME) { |
/* are we already being traced? */ |
if (current->flags & PF_PTRACED) |
return -EPERM; |
/* set the ptrace bit in the process flags. */ |
current->flags |= PF_PTRACED; |
return 0; |
} |
if (pid == 1) /* you may not mess with init */ |
return -EPERM; |
if (!(child = get_task(pid))) |
return -ESRCH; |
if (request == PTRACE_ATTACH) { |
if (child == current) |
return -EPERM; |
if ((!child->dumpable || |
(current->uid != child->euid) || |
(current->uid != child->suid) || |
(current->uid != child->uid) || |
(current->gid != child->egid) || |
(current->gid != child->sgid) || |
(current->gid != child->gid)) && !suser()) |
return -EPERM; |
/* the same process cannot be attached many times */ |
if (child->flags & PF_PTRACED) |
return -EPERM; |
child->flags |= PF_PTRACED; |
if (child->p_pptr != current) { |
REMOVE_LINKS(child); |
child->p_pptr = current; |
SET_LINKS(child); |
} |
send_sig(SIGSTOP, child, 1); |
return 0; |
} |
if (!(child->flags & PF_PTRACED)) |
return -ESRCH; |
if (child->state != TASK_STOPPED) { |
if (request != PTRACE_KILL) |
return -ESRCH; |
} |
if (child->p_pptr != current) |
return -ESRCH; |
|
switch (request) { |
/* when I and D space are separate, these will need to be fixed. */ |
case PTRACE_PEEKTEXT: /* read word at location addr. */ |
case PTRACE_PEEKDATA: { |
unsigned long tmp; |
int res; |
|
res = read_long(child, addr, &tmp); |
if (res < 0) |
return res; |
res = verify_area(VERIFY_WRITE, (void *) data, sizeof(long)); |
if (!res) |
put_fs_long(tmp,(unsigned long *) data); |
return res; |
} |
|
/* read the word at location addr in the USER area. */ |
case PTRACE_PEEKUSR: { |
unsigned long tmp; |
int res; |
|
if ((addr & 3) || addr < 0 |
|| addr > sizeof(struct user) - 3) |
return -EIO; |
|
res = verify_area(VERIFY_WRITE, (void *) data, sizeof(long)); |
if (res) |
return res; |
tmp = 0; /* Default return condition */ |
if(addr < 17*sizeof(long)) |
tmp = getreg(child, addr); |
else if(addr >= (long) &dummy->u_debugreg[0] |
&& addr <= (long) &dummy->u_debugreg[7]) |
{ |
addr -= (long) &dummy->u_debugreg[0]; |
addr = addr >> 2; |
tmp = child->debugreg[addr]; |
} |
put_fs_long(tmp,(unsigned long *) data); |
return 0; |
} |
|
/* when I and D space are separate, this will have to be fixed. */ |
case PTRACE_POKETEXT: /* write the word at location addr. */ |
case PTRACE_POKEDATA: |
return write_long(child,addr,data); |
|
case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ |
if ((addr & 3) || addr < 0 |
|| addr > sizeof(struct user) - 3) |
return -EIO; |
|
if(addr < 17*sizeof(long)) |
return putreg(child, addr, data); |
|
/* We need to be very careful here. We implicitly |
want to modify a portion of the task_struct, and we |
have to be selective about what portions we allow someone |
to modify. */ |
|
if(addr >= (long) &dummy->u_debugreg[0] && |
addr <= (long) &dummy->u_debugreg[7]){ |
|
if(addr == (long) &dummy->u_debugreg[4]) return -EIO; |
if(addr == (long) &dummy->u_debugreg[5]) return -EIO; |
if(addr < (long) &dummy->u_debugreg[4] && |
((unsigned long) data) >= 0xbffffffd) return -EIO; |
|
if(addr == (long) &dummy->u_debugreg[7]) { |
data &= ~DR_CONTROL_RESERVED; |
for(i=0; i<4; i++) |
if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1) |
return -EIO; |
}; |
|
addr -= (long) &dummy->u_debugreg; |
addr = addr >> 2; |
child->debugreg[addr] = data; |
return 0; |
}; |
return -EIO; |
|
case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ |
case PTRACE_CONT: { /* restart after signal. */ |
long tmp; |
|
if ((unsigned long) data > NSIG) |
return -EIO; |
if (request == PTRACE_SYSCALL) |
child->flags |= PF_TRACESYS; |
else |
child->flags &= ~PF_TRACESYS; |
child->exit_code = data; |
wake_up_process(child); |
/* make sure the single step bit is not set. */ |
tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; |
put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); |
return 0; |
} |
|
/* |
* make the child exit. Best I can do is send it a sigkill. |
* perhaps it should be put in the status that it wants to |
* exit. |
*/ |
case PTRACE_KILL: { |
long tmp; |
|
if (child->state == TASK_ZOMBIE) /* already dead */ |
return 0; |
wake_up_process(child); |
child->exit_code = SIGKILL; |
/* make sure the single step bit is not set. */ |
tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; |
put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); |
return 0; |
} |
|
case PTRACE_SINGLESTEP: { /* set the trap flag. */ |
long tmp; |
|
if ((unsigned long) data > NSIG) |
return -EIO; |
child->flags &= ~PF_TRACESYS; |
tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) | TRAP_FLAG; |
put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); |
wake_up_process(child); |
child->exit_code = data; |
/* give it a chance to run. */ |
return 0; |
} |
|
case PTRACE_DETACH: { /* detach a process that was attached. */ |
long tmp; |
|
if ((unsigned long) data > NSIG) |
return -EIO; |
child->flags &= ~(PF_PTRACED|PF_TRACESYS); |
wake_up_process(child); |
child->exit_code = data; |
REMOVE_LINKS(child); |
child->p_pptr = child->p_opptr; |
SET_LINKS(child); |
/* make sure the single step bit is not set. */ |
tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; |
put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); |
return 0; |
} |
|
case PTRACE_GETREGS: { /* Get all gp regs from the child. */ |
#ifdef CONFIG_MATH_EMULATION |
if (!hard_math) |
/* Not supported. */ |
return -EIO; |
#endif |
|
if (verify_area(VERIFY_WRITE, (void *) data, |
17*sizeof(long))) |
return -EIO; |
for (i = 0; i < 17*sizeof(long); |
i += sizeof(long), data += sizeof(long)) |
put_fs_long (getreg(child, i), (unsigned long *) data); |
return 0; |
}; |
|
case PTRACE_SETREGS: { /* Set all gp regs in the child. */ |
unsigned long tmp; |
|
#ifdef CONFIG_MATH_EMULATION |
if (!hard_math) |
/* Not supported. */ |
return -EIO; |
#endif |
|
if (verify_area(VERIFY_READ, (void *) data, |
17*sizeof(long))) |
return -EIO; |
for (i = 0; i < 17*sizeof(long); |
i += sizeof(long), data += sizeof(long)) |
{ |
tmp = get_fs_long ((unsigned long *) data); |
putreg(child, i, tmp); |
} |
return 0; |
}; |
|
case PTRACE_GETFPREGS: { /* Get the child FPU state. */ |
unsigned long *tmp; |
|
#ifdef CONFIG_MATH_EMULATION |
if (!hard_math) |
/* Not supported. */ |
return -EIO; |
#endif |
|
if (verify_area(VERIFY_WRITE, (void *) data, |
sizeof(struct user_i387_struct))) |
return -EIO; |
if ( !child->used_math ) { |
/* Simulate an empty FPU. */ |
child->tss.i387.hard.cwd = 0xffff037f; |
child->tss.i387.hard.swd = 0xffff0000; |
child->tss.i387.hard.twd = 0xffffffff; |
} |
if (last_task_used_math == child) |
{ |
clts(); |
__asm__("fnsave %0; fwait":"=m" (child->tss.i387.hard)); |
last_task_used_math = NULL; |
stts(); |
} |
tmp = (unsigned long *) &child->tss.i387.hard; |
for ( i = 0; i < sizeof(struct user_i387_struct); i += sizeof(long) ) |
{ |
put_fs_long (*tmp, (unsigned long *) data); |
data += sizeof(long); |
tmp++; |
} |
|
return 0; |
}; |
|
case PTRACE_SETFPREGS: { /* Set the child FPU state. */ |
unsigned long *tmp; |
|
#ifdef CONFIG_MATH_EMULATION |
if (!hard_math) |
/* Not supported. */ |
return -EIO; |
#endif |
|
if (verify_area(VERIFY_READ, (void *) data, |
sizeof(struct user_i387_struct))) |
return -EIO; |
child->used_math = 1; |
if (last_task_used_math == child) |
{ |
/* Discard the state of the FPU */ |
last_task_used_math = NULL; |
} |
tmp = (unsigned long *) &child->tss.i387.hard; |
for ( i = 0; i < sizeof(struct user_i387_struct); i += sizeof(long) ) |
{ |
*tmp = get_fs_long ((unsigned long *) data); |
data += sizeof(long); |
tmp++; |
} |
child->flags &= ~PF_USEDFPU; |
return 0; |
}; |
|
default: |
return -EIO; |
} |
} |
|
asmlinkage void syscall_trace(void) |
{ |
if ((current->flags & (PF_PTRACED|PF_TRACESYS)) |
!= (PF_PTRACED|PF_TRACESYS)) |
return; |
current->exit_code = SIGTRAP; |
current->state = TASK_STOPPED; |
notify_parent(current, SIGCHLD); |
schedule(); |
/* |
* this isn't the same as continuing with a signal, but it will do |
* for normal use. strace only continues with a signal if the |
* stopping signal is not SIGTRAP. -brl |
*/ |
if (current->exit_code) |
current->signal |= (1 << (current->exit_code - 1)); |
current->exit_code = 0; |
} |
|
void get_pt_regs_for_task(struct pt_regs *regs, struct task_struct *task) |
{ |
*regs = *(struct pt_regs *) (((unsigned char *) task->tss.esp0) - MAGICNUMBER); |
} |
|
/entry.S
0,0 → 1,710
/* |
* linux/arch/i386/entry.S |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
*/ |
|
/* |
* entry.S contains the system-call and fault low-level handling routines. |
* This also contains the timer-interrupt handler, as well as all interrupts |
* and faults that can result in a task-switch. |
* |
* NOTE: This code handles signal-recognition, which happens every time |
* after a timer-interrupt and after each system call. |
* |
* I changed all the .align's to 4 (16 byte alignment), as that's faster |
* on a 486. |
* |
* Stack layout in 'ret_from_system_call': |
* ptrace needs to have all regs on the stack. |
* if the order here is changed, it needs to be |
* updated in fork.c:copy_process, signal.c:do_signal, |
* ptrace.c and ptrace.h |
* |
* 0(%esp) - %ebx |
* 4(%esp) - %ecx |
* 8(%esp) - %edx |
* C(%esp) - %esi |
* 10(%esp) - %edi |
* 14(%esp) - %ebp |
* 18(%esp) - %eax |
* 1C(%esp) - %ds |
* 20(%esp) - %es |
* 24(%esp) - %fs |
* 28(%esp) - %gs |
* 2C(%esp) - orig_eax |
* 30(%esp) - %eip |
* 34(%esp) - %cs |
* 38(%esp) - %eflags |
* 3C(%esp) - %oldesp |
* 40(%esp) - %oldss |
*/ |
|
#include <linux/sys.h> |
#include <linux/linkage.h> |
#include <asm/segment.h> |
#define ASSEMBLY |
#include <asm/smp.h> |
|
EBX = 0x00 |
ECX = 0x04 |
EDX = 0x08 |
ESI = 0x0C |
EDI = 0x10 |
EBP = 0x14 |
EAX = 0x18 |
DS = 0x1C |
ES = 0x20 |
FS = 0x24 |
GS = 0x28 |
ORIG_EAX = 0x2C |
EIP = 0x30 |
CS = 0x34 |
EFLAGS = 0x38 |
OLDESP = 0x3C |
OLDSS = 0x40 |
|
CF_MASK = 0x00000001 |
IF_MASK = 0x00000200 |
NT_MASK = 0x00004000 |
VM_MASK = 0x00020000 |
|
/* |
* these are offsets into the task-struct. |
*/ |
state = 0 |
counter = 4 |
priority = 8 |
signal = 12 |
blocked = 16 |
flags = 20 |
dbgreg6 = 52 |
dbgreg7 = 56 |
exec_domain = 60 |
|
ENOSYS = 38 |
|
#define SAVE_ALL \ |
cld; \ |
push %gs; \ |
push %fs; \ |
push %es; \ |
push %ds; \ |
pushl %eax; \ |
pushl %ebp; \ |
pushl %edi; \ |
pushl %esi; \ |
pushl %edx; \ |
pushl %ecx; \ |
pushl %ebx; \ |
movl $(KERNEL_DS),%edx; \ |
mov %dx,%ds; \ |
mov %dx,%es; \ |
movl $(USER_DS),%edx; \ |
mov %dx,%fs; |
|
#ifdef __SMP__ |
|
#define GET_PROCESSOR_ID \ |
movl SYMBOL_NAME(apic_reg), %edx; \ |
movl 32(%edx), %eax;\ |
movl %eax,SYMBOL_NAME(apic_retval); \ |
shrl $24,%eax; \ |
andb $0x0F,%al; |
|
/* |
* Get the processor ID multiplied by 4 |
*/ |
|
#define GET_PROCESSOR_OFFSET(x) \ |
movl SYMBOL_NAME(apic_reg), x ; \ |
movl 32( x ), x ; \ |
shrl $22, x ; \ |
andl $0x3C, x ; |
|
/* macro LEAVE_KERNEL decrements kernel_counter and resets kernel_flag and |
saves processor variables if zero */ |
#define LEAVE_KERNEL \ |
pushfl; \ |
cli; \ |
GET_PROCESSOR_ID \ |
btrl $ SMP_FROM_SYSCALL,SYMBOL_NAME(smp_proc_in_lock)(,%eax,4); \ |
decl SYMBOL_NAME(syscall_count); \ |
decl SYMBOL_NAME(kernel_counter); \ |
jnz 1f; \ |
movb SYMBOL_NAME(saved_active_kernel_processor), %al; \ |
movb %al, SYMBOL_NAME(active_kernel_processor); \ |
cmpb $(NO_PROC_ID), %al; \ |
jnz 1f; \ |
lock; \ |
btrl $0, SYMBOL_NAME(kernel_flag); \ |
1: popfl; |
|
/* macro ENTER_KERNEL waits for entering the kernel, increments |
kernel_counter, and reloads the processor variables if necessary |
uses : %eax, %edx (pushed and popped) |
|
Note: We go to great pains to minimise the number of locked operations. |
We want to spin without locking, and lock when we attempt an update. |
The pentium has a MESI cache so the spin without lock will exit when |
another CPU write invalidates our cache, and the lock is avoided when |
possible so we don't play ping-pong games with the cache line. |
|
*/ |
|
#ifndef __SMP_PROF__ |
|
#define SMP_PROF_A |
#define SMP_PROF_B |
|
#else |
|
#define SMP_PROF_A movl $0,SYMBOL_NAME(smp_spins_syscall_cur)(,%eax,4); |
#define SMP_PROF_B incl SYMBOL_NAME(smp_spins_syscall)(,%eax,4); \ |
incl SYMBOL_NAME(smp_spins_syscall_cur)(,%eax,4); |
#endif |
|
#define ENTER_KERNEL \ |
pushl %eax; \ |
pushl %ebx; \ |
pushl %ecx; \ |
pushl %edx; \ |
pushfl; \ |
cli; \ |
movl $6000, %ebx; \ |
movl SYMBOL_NAME(smp_loops_per_tick), %ecx; \ |
GET_PROCESSOR_ID \ |
btsl $ SMP_FROM_SYSCALL,SYMBOL_NAME(smp_proc_in_lock)(,%eax,4); \ |
SMP_PROF_A \ |
1: lock; \ |
btsl $0, SYMBOL_NAME(kernel_flag); \ |
jnc 3f; \ |
cmpb SYMBOL_NAME(active_kernel_processor), %al; \ |
je 4f; \ |
2: SMP_PROF_B \ |
btl %eax, SYMBOL_NAME(smp_invalidate_needed); \ |
jnc 5f; \ |
lock; \ |
btrl %eax, SYMBOL_NAME(smp_invalidate_needed); \ |
jnc 5f; \ |
movl %cr3,%edx; \ |
movl %edx,%cr3; \ |
5: sti; \ |
decl %ecx; \ |
cli; \ |
jne 7f; \ |
decl %ebx; \ |
jne 6f; \ |
call SYMBOL_NAME(non_irq_deadlock_detected); \ |
6: movl SYMBOL_NAME(smp_loops_per_tick), %ecx; \ |
cmpb SYMBOL_NAME(boot_cpu_id), %al; \ |
jne 7f; \ |
incl SYMBOL_NAME(jiffies); \ |
7: btl $0, SYMBOL_NAME(kernel_flag); \ |
jc 2b; \ |
jmp 1b; \ |
3: movb %al, SYMBOL_NAME(active_kernel_processor); \ |
4: incl SYMBOL_NAME(kernel_counter); \ |
incl SYMBOL_NAME(syscall_count); \ |
popfl; \ |
popl %edx; \ |
popl %ecx; \ |
popl %ebx; \ |
popl %eax; |
|
|
#define RESTORE_ALL \ |
cmpw $(KERNEL_CS),CS(%esp); \ |
je 1f; \ |
GET_PROCESSOR_OFFSET(%edx) \ |
movl SYMBOL_NAME(current_set)(,%edx), %eax ; ; \ |
movl dbgreg7(%eax),%ebx; \ |
movl %ebx,%db7; \ |
1: LEAVE_KERNEL \ |
popl %ebx; \ |
popl %ecx; \ |
popl %edx; \ |
popl %esi; \ |
popl %edi; \ |
popl %ebp; \ |
popl %eax; \ |
pop %ds; \ |
pop %es; \ |
pop %fs; \ |
pop %gs; \ |
addl $4,%esp; \ |
iret |
|
#else |
|
#define RESTORE_ALL \ |
cmpw $(KERNEL_CS),CS(%esp); \ |
je 1f; \ |
movl SYMBOL_NAME(current_set),%eax; \ |
movl dbgreg7(%eax),%ebx; \ |
movl %ebx,%db7; \ |
1: \ |
popl %ebx; \ |
popl %ecx; \ |
popl %edx; \ |
popl %esi; \ |
popl %edi; \ |
popl %ebp; \ |
popl %eax; \ |
pop %ds; \ |
pop %es; \ |
pop %fs; \ |
pop %gs; \ |
addl $4,%esp; \ |
iret |
#endif |
|
ENTRY(lcall7) |
pushfl # We get a different stack layout with call gates, |
pushl %eax # which has to be cleaned up later.. |
SAVE_ALL |
#ifdef __SMP__ |
ENTER_KERNEL |
#endif |
movl EIP(%esp),%eax # due to call gates, this is eflags, not eip.. |
movl CS(%esp),%edx # this is eip.. |
movl EFLAGS(%esp),%ecx # and this is cs.. |
movl %eax,EFLAGS(%esp) # |
movl %edx,EIP(%esp) # Now we move them to their "normal" places |
movl %ecx,CS(%esp) # |
movl %esp,%eax |
#ifdef __SMP__ |
GET_PROCESSOR_OFFSET(%edx) # Processor offset into edx |
movl SYMBOL_NAME(current_set)(,%edx),%edx |
#else |
movl SYMBOL_NAME(current_set),%edx |
#endif |
pushl %eax |
movl exec_domain(%edx),%edx # Get the execution domain |
movl 4(%edx),%edx # Get the lcall7 handler for the domain |
call *%edx |
popl %eax |
jmp ret_from_sys_call |
|
ALIGN |
handle_bottom_half: |
incl SYMBOL_NAME(intr_count) |
call SYMBOL_NAME(do_bottom_half) |
decl SYMBOL_NAME(intr_count) |
jmp 9f |
ALIGN |
reschedule: |
pushl $ret_from_sys_call |
jmp SYMBOL_NAME(schedule) # test |
|
ENTRY(system_call) |
pushl %eax # save orig_eax |
SAVE_ALL |
#ifdef __SMP__ |
ENTER_KERNEL |
#endif |
movl $-ENOSYS,EAX(%esp) |
cmpl $(NR_syscalls),%eax |
jae ret_from_sys_call |
movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax |
testl %eax,%eax |
je ret_from_sys_call |
#ifdef __SMP__ |
GET_PROCESSOR_OFFSET(%edx) |
movl SYMBOL_NAME(current_set)(,%edx),%ebx |
#else |
movl SYMBOL_NAME(current_set),%ebx |
#endif |
andl $~CF_MASK,EFLAGS(%esp) # clear carry - assume no errors |
movl %db6,%edx |
movl %edx,dbgreg6(%ebx) # save current hardware debugging status |
testb $0x20,flags(%ebx) # PF_TRACESYS |
jne 1f |
call *%eax |
movl %eax,EAX(%esp) # save the return value |
jmp ret_from_sys_call |
ALIGN |
1: call SYMBOL_NAME(syscall_trace) |
movl ORIG_EAX(%esp),%eax |
call *SYMBOL_NAME(sys_call_table)(,%eax,4) |
movl %eax,EAX(%esp) # save the return value |
#ifdef __SMP__ |
GET_PROCESSOR_OFFSET(%eax) |
movl SYMBOL_NAME(current_set)(,%eax),%eax |
#else |
movl SYMBOL_NAME(current_set),%eax |
#endif |
call SYMBOL_NAME(syscall_trace) |
|
ALIGN |
.globl ret_from_sys_call |
ret_from_sys_call: |
cmpl $0,SYMBOL_NAME(intr_count) |
jne 2f |
9: movl SYMBOL_NAME(bh_mask),%eax |
andl SYMBOL_NAME(bh_active),%eax |
jne handle_bottom_half |
#ifdef __SMP__ |
cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor) |
jne 2f |
#endif |
movl EFLAGS(%esp),%eax # check VM86 flag: CS/SS are |
testl $(VM_MASK),%eax # different then |
jne 1f |
cmpw $(KERNEL_CS),CS(%esp) # was old code segment supervisor ? |
je 2f |
1: sti |
orl $(IF_MASK),%eax # these just try to make sure |
andl $~NT_MASK,%eax # the program doesn't do anything |
movl %eax,EFLAGS(%esp) # stupid |
cmpl $0,SYMBOL_NAME(need_resched) |
jne reschedule |
#ifdef __SMP__ |
GET_PROCESSOR_OFFSET(%eax) |
movl SYMBOL_NAME(current_set)(,%eax), %eax |
#else |
movl SYMBOL_NAME(current_set),%eax |
#endif |
cmpl SYMBOL_NAME(task),%eax # task[0] cannot have signals |
je 2f |
movl blocked(%eax),%ecx |
movl %ecx,%ebx # save blocked in %ebx for signal handling |
notl %ecx |
andl signal(%eax),%ecx |
jne signal_return |
2: RESTORE_ALL |
ALIGN |
.globl signal_return |
signal_return: |
movl %esp,%ecx |
pushl %ecx |
testl $(VM_MASK),EFLAGS(%ecx) |
jne v86_signal_return |
pushl %ebx |
call SYMBOL_NAME(do_signal) |
popl %ebx |
popl %ebx |
RESTORE_ALL |
ALIGN |
v86_signal_return: |
call SYMBOL_NAME(save_v86_state) |
movl %eax,%esp |
pushl %eax |
pushl %ebx |
call SYMBOL_NAME(do_signal) |
popl %ebx |
popl %ebx |
RESTORE_ALL |
|
ENTRY(divide_error) |
pushl $0 # no error code |
pushl $ SYMBOL_NAME(do_divide_error) |
ALIGN |
error_code: |
push %fs |
push %es |
push %ds |
pushl %eax |
xorl %eax,%eax |
pushl %ebp |
pushl %edi |
pushl %esi |
pushl %edx |
decl %eax # eax = -1 |
pushl %ecx |
pushl %ebx |
cld |
xorl %ebx,%ebx # zero ebx |
xchgl %eax, ORIG_EAX(%esp) # orig_eax (get the error code. ) |
mov %gs,%bx # get the lower order bits of gs |
movl %esp,%edx |
xchgl %ebx, GS(%esp) # get the address and save gs. |
pushl %eax # push the error code |
pushl %edx |
movl $(KERNEL_DS),%edx |
mov %dx,%ds |
mov %dx,%es |
movl $(USER_DS),%edx |
mov %dx,%fs |
#ifdef __SMP__ |
ENTER_KERNEL |
GET_PROCESSOR_OFFSET(%eax) |
movl SYMBOL_NAME(current_set)(,%eax), %eax |
#else |
movl SYMBOL_NAME(current_set),%eax |
#endif |
movl %db6,%edx |
movl %edx,dbgreg6(%eax) # save current hardware debugging status |
call *%ebx |
addl $8,%esp |
jmp ret_from_sys_call |
|
ENTRY(coprocessor_error) |
pushl $0 |
pushl $ SYMBOL_NAME(do_coprocessor_error) |
jmp error_code |
|
ENTRY(device_not_available) |
pushl $-1 # mark this as an int |
SAVE_ALL |
#ifdef __SMP__ |
ENTER_KERNEL |
#endif |
pushl $ret_from_sys_call |
movl %cr0,%eax |
testl $0x4,%eax # EM (math emulation bit) |
je SYMBOL_NAME(math_state_restore) |
pushl $0 # temporary storage for ORIG_EIP |
call SYMBOL_NAME(math_emulate) |
addl $4,%esp |
ret |
|
ENTRY(debug) |
pushl $0 |
pushl $ SYMBOL_NAME(do_debug) |
jmp error_code |
|
ENTRY(nmi) |
pushl $0 |
pushl $ SYMBOL_NAME(do_nmi) |
jmp error_code |
|
ENTRY(int3) |
pushl $0 |
pushl $ SYMBOL_NAME(do_int3) |
jmp error_code |
|
ENTRY(overflow) |
pushl $0 |
pushl $ SYMBOL_NAME(do_overflow) |
jmp error_code |
|
ENTRY(bounds) |
pushl $0 |
pushl $ SYMBOL_NAME(do_bounds) |
jmp error_code |
|
ENTRY(invalid_op) |
pushl $0 |
pushl $ SYMBOL_NAME(do_invalid_op) |
jmp error_code |
|
ENTRY(coprocessor_segment_overrun) |
pushl $0 |
pushl $ SYMBOL_NAME(do_coprocessor_segment_overrun) |
jmp error_code |
|
ENTRY(reserved) |
pushl $0 |
pushl $ SYMBOL_NAME(do_reserved) |
jmp error_code |
|
ENTRY(double_fault) |
pushl $ SYMBOL_NAME(do_double_fault) |
jmp error_code |
|
ENTRY(invalid_TSS) |
pushl $ SYMBOL_NAME(do_invalid_TSS) |
jmp error_code |
|
ENTRY(segment_not_present) |
pushl $ SYMBOL_NAME(do_segment_not_present) |
jmp error_code |
|
ENTRY(stack_segment) |
pushl $ SYMBOL_NAME(do_stack_segment) |
jmp error_code |
|
ENTRY(general_protection) |
pushl $ SYMBOL_NAME(do_general_protection) |
jmp error_code |
|
ENTRY(alignment_check) |
pushl $ SYMBOL_NAME(do_alignment_check) |
jmp error_code |
|
ENTRY(page_fault) |
pushl $ SYMBOL_NAME(do_page_fault) |
jmp error_code |
|
ENTRY(spurious_interrupt_bug) |
pushl $0 |
pushl $ SYMBOL_NAME(do_spurious_interrupt_bug) |
jmp error_code |
|
.data |
ENTRY(sys_call_table) |
.long SYMBOL_NAME(sys_setup) /* 0 */ |
.long SYMBOL_NAME(sys_exit) |
.long SYMBOL_NAME(sys_fork) |
.long SYMBOL_NAME(sys_read) |
.long SYMBOL_NAME(sys_write) |
.long SYMBOL_NAME(sys_open) /* 5 */ |
.long SYMBOL_NAME(sys_close) |
.long SYMBOL_NAME(sys_waitpid) |
.long SYMBOL_NAME(sys_creat) |
.long SYMBOL_NAME(sys_link) |
.long SYMBOL_NAME(sys_unlink) /* 10 */ |
.long SYMBOL_NAME(sys_execve) |
.long SYMBOL_NAME(sys_chdir) |
.long SYMBOL_NAME(sys_time) |
.long SYMBOL_NAME(sys_mknod) |
.long SYMBOL_NAME(sys_chmod) /* 15 */ |
.long SYMBOL_NAME(sys_chown) |
.long SYMBOL_NAME(sys_break) |
.long SYMBOL_NAME(sys_stat) |
.long SYMBOL_NAME(sys_lseek) |
.long SYMBOL_NAME(sys_getpid) /* 20 */ |
.long SYMBOL_NAME(sys_mount) |
.long SYMBOL_NAME(sys_umount) |
.long SYMBOL_NAME(sys_setuid) |
.long SYMBOL_NAME(sys_getuid) |
.long SYMBOL_NAME(sys_stime) /* 25 */ |
.long SYMBOL_NAME(sys_ptrace) |
.long SYMBOL_NAME(sys_alarm) |
.long SYMBOL_NAME(sys_fstat) |
.long SYMBOL_NAME(sys_pause) |
.long SYMBOL_NAME(sys_utime) /* 30 */ |
.long SYMBOL_NAME(sys_stty) |
.long SYMBOL_NAME(sys_gtty) |
.long SYMBOL_NAME(sys_access) |
.long SYMBOL_NAME(sys_nice) |
.long SYMBOL_NAME(sys_ftime) /* 35 */ |
.long SYMBOL_NAME(sys_sync) |
.long SYMBOL_NAME(sys_kill) |
.long SYMBOL_NAME(sys_rename) |
.long SYMBOL_NAME(sys_mkdir) |
.long SYMBOL_NAME(sys_rmdir) /* 40 */ |
.long SYMBOL_NAME(sys_dup) |
.long SYMBOL_NAME(sys_pipe) |
.long SYMBOL_NAME(sys_times) |
.long SYMBOL_NAME(sys_prof) |
.long SYMBOL_NAME(sys_brk) /* 45 */ |
.long SYMBOL_NAME(sys_setgid) |
.long SYMBOL_NAME(sys_getgid) |
.long SYMBOL_NAME(sys_signal) |
.long SYMBOL_NAME(sys_geteuid) |
.long SYMBOL_NAME(sys_getegid) /* 50 */ |
.long SYMBOL_NAME(sys_acct) |
.long SYMBOL_NAME(sys_phys) |
.long SYMBOL_NAME(sys_lock) |
.long SYMBOL_NAME(sys_ioctl) |
.long SYMBOL_NAME(sys_fcntl) /* 55 */ |
.long SYMBOL_NAME(sys_mpx) |
.long SYMBOL_NAME(sys_setpgid) |
.long SYMBOL_NAME(sys_ulimit) |
.long SYMBOL_NAME(sys_olduname) |
.long SYMBOL_NAME(sys_umask) /* 60 */ |
.long SYMBOL_NAME(sys_chroot) |
.long SYMBOL_NAME(sys_ustat) |
.long SYMBOL_NAME(sys_dup2) |
.long SYMBOL_NAME(sys_getppid) |
.long SYMBOL_NAME(sys_getpgrp) /* 65 */ |
.long SYMBOL_NAME(sys_setsid) |
.long SYMBOL_NAME(sys_sigaction) |
.long SYMBOL_NAME(sys_sgetmask) |
.long SYMBOL_NAME(sys_ssetmask) |
.long SYMBOL_NAME(sys_setreuid) /* 70 */ |
.long SYMBOL_NAME(sys_setregid) |
.long SYMBOL_NAME(sys_sigsuspend) |
.long SYMBOL_NAME(sys_sigpending) |
.long SYMBOL_NAME(sys_sethostname) |
.long SYMBOL_NAME(sys_setrlimit) /* 75 */ |
.long SYMBOL_NAME(sys_getrlimit) |
.long SYMBOL_NAME(sys_getrusage) |
.long SYMBOL_NAME(sys_gettimeofday) |
.long SYMBOL_NAME(sys_settimeofday) |
.long SYMBOL_NAME(sys_getgroups) /* 80 */ |
.long SYMBOL_NAME(sys_setgroups) |
.long SYMBOL_NAME(old_select) |
.long SYMBOL_NAME(sys_symlink) |
.long SYMBOL_NAME(sys_lstat) |
.long SYMBOL_NAME(sys_readlink) /* 85 */ |
.long SYMBOL_NAME(sys_uselib) |
.long SYMBOL_NAME(sys_swapon) |
.long SYMBOL_NAME(sys_reboot) |
.long SYMBOL_NAME(old_readdir) |
.long SYMBOL_NAME(old_mmap) /* 90 */ |
.long SYMBOL_NAME(sys_munmap) |
.long SYMBOL_NAME(sys_truncate) |
.long SYMBOL_NAME(sys_ftruncate) |
.long SYMBOL_NAME(sys_fchmod) |
.long SYMBOL_NAME(sys_fchown) /* 95 */ |
.long SYMBOL_NAME(sys_getpriority) |
.long SYMBOL_NAME(sys_setpriority) |
.long SYMBOL_NAME(sys_profil) |
.long SYMBOL_NAME(sys_statfs) |
.long SYMBOL_NAME(sys_fstatfs) /* 100 */ |
.long SYMBOL_NAME(sys_ioperm) |
.long SYMBOL_NAME(sys_socketcall) |
.long SYMBOL_NAME(sys_syslog) |
.long SYMBOL_NAME(sys_setitimer) |
.long SYMBOL_NAME(sys_getitimer) /* 105 */ |
.long SYMBOL_NAME(sys_newstat) |
.long SYMBOL_NAME(sys_newlstat) |
.long SYMBOL_NAME(sys_newfstat) |
.long SYMBOL_NAME(sys_uname) |
.long SYMBOL_NAME(sys_iopl) /* 110 */ |
.long SYMBOL_NAME(sys_vhangup) |
.long SYMBOL_NAME(sys_idle) |
.long SYMBOL_NAME(sys_vm86old) |
.long SYMBOL_NAME(sys_wait4) |
.long SYMBOL_NAME(sys_swapoff) /* 115 */ |
.long SYMBOL_NAME(sys_sysinfo) |
.long SYMBOL_NAME(sys_ipc) |
.long SYMBOL_NAME(sys_fsync) |
.long SYMBOL_NAME(sys_sigreturn) |
.long SYMBOL_NAME(sys_clone) /* 120 */ |
.long SYMBOL_NAME(sys_setdomainname) |
.long SYMBOL_NAME(sys_newuname) |
.long SYMBOL_NAME(sys_modify_ldt) |
.long SYMBOL_NAME(sys_adjtimex) |
.long SYMBOL_NAME(sys_mprotect) /* 125 */ |
.long SYMBOL_NAME(sys_sigprocmask) |
.long SYMBOL_NAME(sys_create_module) |
.long SYMBOL_NAME(sys_init_module) |
.long SYMBOL_NAME(sys_delete_module) |
.long SYMBOL_NAME(sys_get_kernel_syms) /* 130 */ |
.long SYMBOL_NAME(sys_quotactl) |
.long SYMBOL_NAME(sys_getpgid) |
.long SYMBOL_NAME(sys_fchdir) |
.long SYMBOL_NAME(sys_bdflush) |
.long SYMBOL_NAME(sys_sysfs) /* 135 */ |
.long SYMBOL_NAME(sys_personality) |
.long 0 /* for afs_syscall */ |
.long SYMBOL_NAME(sys_setfsuid) |
.long SYMBOL_NAME(sys_setfsgid) |
.long SYMBOL_NAME(sys_llseek) /* 140 */ |
.long SYMBOL_NAME(sys_getdents) |
.long SYMBOL_NAME(sys_select) |
.long SYMBOL_NAME(sys_flock) |
.long SYMBOL_NAME(sys_msync) |
.long SYMBOL_NAME(sys_readv) /* 145 */ |
.long SYMBOL_NAME(sys_writev) |
.long SYMBOL_NAME(sys_getsid) |
.long SYMBOL_NAME(sys_fdatasync) |
.long SYMBOL_NAME(sys_sysctl) |
.long SYMBOL_NAME(sys_mlock) /* 150 */ |
.long SYMBOL_NAME(sys_munlock) |
.long SYMBOL_NAME(sys_mlockall) |
.long SYMBOL_NAME(sys_munlockall) |
.long SYMBOL_NAME(sys_sched_setparam) |
.long SYMBOL_NAME(sys_sched_getparam) /* 155 */ |
.long SYMBOL_NAME(sys_sched_setscheduler) |
.long SYMBOL_NAME(sys_sched_getscheduler) |
.long SYMBOL_NAME(sys_sched_yield) |
.long SYMBOL_NAME(sys_sched_get_priority_max) |
.long SYMBOL_NAME(sys_sched_get_priority_min) /* 160 */ |
.long SYMBOL_NAME(sys_sched_rr_get_interval) |
.long SYMBOL_NAME(sys_nanosleep) |
.long SYMBOL_NAME(sys_mremap) |
.long 0,0 |
.long SYMBOL_NAME(sys_vm86) /* 166 */ |
.long 0 /* 167 */ |
.long 0 /* 168 STREAMS poll */ |
.long 0 /* 169 */ |
.long 0,0,0,0,0,0,0,0,0,0 /* 170 - 179 */ |
.long 0,0,0,0,0,0,0,0 /* 180 - 187 */ |
.long 0 /* 188 STREAMS getpmsg */ |
.long 0 /* 189 STREAMS putpmsg */ |
.space (NR_syscalls-189)*4 |
/head.S
0,0 → 1,523
/* |
* linux/arch/i386/head.S |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
*/ |
|
/* |
* head.S contains the 32-bit startup code. |
*/ |
|
.text |
#include <linux/config.h> |
#include <linux/tasks.h> |
#include <linux/linkage.h> |
#include <asm/segment.h> |
#include <linux/config.h> |
#include <asm/page.h> |
#include <asm/pgtable.h> |
|
|
#define CL_MAGIC_ADDR 0x90020 |
#define CL_MAGIC 0xA33F |
#define CL_BASE_ADDR 0x90000 |
#define CL_OFFSET 0x90022 |
|
/* |
* swapper_pg_dir is the main page directory, address 0x00001000 (or at |
* address 0x00101000 for a compressed boot). |
*/ |
ENTRY(stext) |
ENTRY(_stext) |
startup_32: |
cld |
movl $(KERNEL_DS),%eax |
mov %ax,%ds |
mov %ax,%es |
mov %ax,%fs |
mov %ax,%gs |
#ifdef __SMP__ |
orw %bx,%bx |
jz 1f /* Initial CPU cleans BSS */ |
/* |
* Set up the stack |
*/ |
mov %ax,%ss |
xorl %eax,%eax |
movw %cx, %ax |
movl %eax,%esp |
pushl $0 |
popfl |
jmp checkCPUtype |
1: |
lss stack_start,%esp |
#endif __SMP__ |
/* |
* Clear BSS first so that there are no surprises... |
*/ |
xorl %eax,%eax |
movl $ SYMBOL_NAME(_edata),%edi |
movl $ SYMBOL_NAME(_end),%ecx |
subl %edi,%ecx |
cld |
rep |
stosb |
/* |
* start system 32-bit setup. We need to re-do some of the things done |
* in 16-bit mode for the "real" operations. |
*/ |
call setup_idt |
xorl %eax,%eax |
1: incl %eax # check that A20 really IS enabled |
movl %eax,0x000000 # loop forever if it isn't |
cmpl %eax,0x100000 |
je 1b |
/* |
* Initialize eflags. Some BIOS's leave bits like NT set. This would |
* confuse the debugger if this code is traced. |
* XXX - best to initialize before switching to protected mode. |
*/ |
pushl $0 |
popfl |
/* |
* Copy bootup parameters out of the way. First 2kB of |
* _empty_zero_page is for boot parameters, second 2kB |
* is for the command line. |
*/ |
movl $0x90000,%esi |
movl $ SYMBOL_NAME(empty_zero_page),%edi |
movl $512,%ecx |
cld |
rep |
movsl |
xorl %eax,%eax |
movl $512,%ecx |
rep |
stosl |
cmpw $(CL_MAGIC),CL_MAGIC_ADDR |
jne 1f |
movl $ SYMBOL_NAME(empty_zero_page)+2048,%edi |
movzwl CL_OFFSET,%esi |
addl $(CL_BASE_ADDR),%esi |
movl $2048,%ecx |
rep |
movsb |
1: |
#ifdef __SMP__ |
checkCPUtype: |
#endif |
|
/* check Processor type: 386, 486, 6x86(L) or CPUID capable processor */ |
/* |
* XXX - this does a lot of unnecessary setup. Alignment checks don't |
* apply at our cpl of 0 and the stack ought to be aligned already, and |
* we don't need to preserve eflags. |
*/ |
|
movl $3, SYMBOL_NAME(x86) |
pushfl # push EFLAGS |
popl %eax # get EFLAGS |
movl %eax,%ecx # save original EFLAGS in ecx |
xorl $0x40000,%eax # flip AC bit in EFLAGS |
pushl %eax # copy to EFLAGS |
popfl # set EFLAGS |
pushfl # get new EFLAGS |
popl %eax # put it in eax |
xorl %ecx,%eax # change in flags |
andl $0x40000,%eax # check if AC bit changed |
je is386 |
movl $4,SYMBOL_NAME(x86) |
movl %ecx,%eax |
xorl $0x200000,%eax # check ID flag |
pushl %eax |
popfl # if we are on a straight 486DX, SX, or |
pushfl # 487SX we can't change it |
popl %eax # Also if we are on a Cyrix 6x86(L) |
xorl %ecx,%eax # OTOH 6x86MXs and MIIs check OK |
andl $0x200000,%eax |
je is486x |
|
isnew: pushl %ecx # restore original EFLAGS |
popfl |
incl SYMBOL_NAME(have_cpuid) # we have CPUID |
/* |
* Technically we should use CPUID 0 to see if we have CPUID 1! |
*/ |
/* get processor type */ |
movl $1, %eax # Use the CPUID instruction to |
#ifdef GAS_KNOWS_CPUID |
cpuid # check the processor type |
#else |
.byte 0x0f, 0xa2 # check the processor type |
#endif |
movb %al, %cl # save reg for future use |
andb $0x0f,%ah # mask processor family |
movb %ah,SYMBOL_NAME(x86) |
andb $0xf0, %al # mask model |
shrb $4, %al |
movb %al,SYMBOL_NAME(x86_model) |
andb $0x0f, %cl # mask mask revision |
movb %cl,SYMBOL_NAME(x86_mask) |
movl %edx,SYMBOL_NAME(x86_capability) |
/* get vendor info */ |
xorl %eax, %eax # call CPUID with 0 -> return vendor ID |
#ifdef GAS_KNOWS_CPUID |
cpuid |
#else |
.byte 0x0f, 0xa2 # CPUID |
#endif |
movl %ebx,SYMBOL_NAME(x86_vendor_id) # lo 4 chars |
movl %edx,SYMBOL_NAME(x86_vendor_id)+4 # next 4 chars |
movl %ecx,SYMBOL_NAME(x86_vendor_id)+8 # last 4 chars |
|
movl %cr0,%eax # 486+ |
andl $0x80000011,%eax # Save PG,PE,ET |
orl $0x50022,%eax # set AM, WP, NE and MP |
jmp 2f |
|
/* Now we test if we have a Cyrix 6x86(L). We didn't test before to avoid |
* clobbering the new BX chipset used with the Pentium II, which has a register |
* at the same addresses as those used to access the Cyrix special configuration |
* registers (CCRs). |
*/ |
/* |
* A Cyrix/IBM 6x86(L) preserves flags after dividing 5 by 2 |
* (and it _must_ be 5 divided by 2) while other CPUs change |
* them in undefined ways. We need to know this since we may |
* need to enable the CPUID instruction at least. |
* We couldn't use this test before since the PPro and PII behave |
* like Cyrix chips in this respect. |
*/ |
is486x: xor %ax,%ax |
sahf |
movb $5,%ax |
movb $2,%bx |
div %bl |
lahf |
cmpb $2,%ah |
jne ncyrix |
/* |
* N.B. The pattern of accesses to 0x22 and 0x23 is *essential* |
* so do not try to "optimize" it! For the same reason we |
* do all this with interrupts off. |
*/ |
#define setCx86(reg, val) \ |
movb reg,%ax; \ |
outb %ax,$0x22; \ |
movb val,%ax; \ |
outb %ax,$0x23 |
|
#define getCx86(reg) \ |
movb reg,%ax; \ |
outb %ax,$0x22; \ |
inb $0x23,%ax |
|
cli |
getCx86($0xc3) # get CCR3 |
movb %ax,%cx # Save old value |
movb %ax,%bx |
andb $0x0f,%bx # Enable access to all config registers |
orb $0x10,%bx # by setting bit 4 |
setCx86($0xc3,%bx) |
|
getCx86($0xfe) # DIR0 : let's check this is a 6x86(L) |
andb $0xf0,%ax # should be 3xh |
cmpb $0x30,%ax # |
jne n6x86 |
|
getCx86($0xe8) # now we can get CCR4 |
orb $0x80,%ax # and set bit 7 (CPUIDEN) |
movb %ax,%bx # to enable CPUID execution |
setCx86($0xe8,%bx) |
|
getCx86($0xe9) # CCR5 : we reset the SLOP bit |
andb $0xfd,%ax # so that udelay calculation |
movb %ax,%bx # is correct on 6x86(L) CPUs |
setCx86($0xe9,%bx) |
setCx86($0xc3,%cx) # Restore old CCR3 |
sti |
jmp isnew # We enabled CPUID now |
|
n6x86: setCx86($0xc3,%cx) # Restore old CCR3 |
sti |
ncyrix: pushl %ecx # restore original EFLAGS |
popfl |
movl %cr0,%eax # 486 |
andl $0x80000011,%eax # Save PG,PE,ET |
orl $0x50022,%eax # set AM, WP, NE and MP |
jmp 2f |
is386: pushl %ecx # restore original EFLAGS |
popfl |
movl %cr0,%eax # 386 |
andl $0x80000011,%eax # Save PG,PE,ET |
orl $2,%eax # set MP |
2: movl %eax,%cr0 |
call check_x87 |
#ifdef __SMP__ |
movb ready,%al |
orb %al,%al |
jz 3f |
movl $ SYMBOL_NAME(swapper_pg_dir), %eax |
movl %eax, %cr3 |
#ifdef GAS_KNOWS_CR4 |
movl %cr4,%eax |
orl $16,%eax |
movl %eax,%cr4 |
#else |
.byte 0x0f,0x20,0xe0 |
orl $16,%eax |
.byte 0x0f,0x22,0xe0 |
#endif |
movl %cr0, %eax |
orl $0x80000000, %eax |
movl %eax, %cr0 |
jmp 4f |
#endif |
3: |
call setup_paging |
#ifdef __SMP__ |
incb ready |
#endif |
4: |
lgdt gdt_descr |
lidt idt_descr |
ljmp $(KERNEL_CS),$1f |
1: movl $(KERNEL_DS),%eax # reload all the segment registers |
mov %ax,%ds # after changing gdt. |
mov %ax,%es |
mov %ax,%fs |
mov %ax,%gs |
#ifdef __SMP__ |
movl $(KERNEL_DS), %eax |
mov %ax,%ss # Reload the stack pointer (segment only) |
#else |
lss stack_start,%esp # Load processor stack |
#endif |
xorl %eax,%eax |
lldt %ax |
pushl %eax # These are the parameters to main :-) |
pushl %eax |
pushl %eax |
cld # gcc2 wants the direction flag cleared at all times |
call SYMBOL_NAME(start_kernel) |
L6: |
jmp L6 # main should never return here, but |
# just in case, we know what happens. |
|
#ifdef __SMP__ |
ready: .byte 0 |
#endif |
|
/* |
* We depend on ET to be correct. This checks for 287/387. |
*/ |
check_x87: |
movb $0,SYMBOL_NAME(hard_math) |
clts |
fninit |
fstsw %ax |
cmpb $0,%al |
je 1f |
movl %cr0,%eax /* no coprocessor: have to set bits */ |
xorl $4,%eax /* set EM */ |
movl %eax,%cr0 |
ret |
ALIGN |
1: movb $1,SYMBOL_NAME(hard_math) |
.byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */ |
ret |
|
/* |
* setup_idt |
* |
* sets up a idt with 256 entries pointing to |
* ignore_int, interrupt gates. It doesn't actually load |
* idt - that can be done only after paging has been enabled |
* and the kernel moved to PAGE_OFFSET. Interrupts |
* are enabled elsewhere, when we can be relatively |
* sure everything is ok. |
*/ |
setup_idt: |
lea ignore_int,%edx |
movl $(KERNEL_CS << 16),%eax |
movw %dx,%ax /* selector = 0x0010 = cs */ |
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ |
|
lea SYMBOL_NAME(idt),%edi |
mov $256,%ecx |
rp_sidt: |
movl %eax,(%edi) |
movl %edx,4(%edi) |
addl $8,%edi |
dec %ecx |
jne rp_sidt |
ret |
|
|
/* |
* Setup_paging |
* |
* This routine sets up paging by setting the page bit |
* in cr0. The page tables are set up, identity-mapping |
* the first 4MB. The rest are initialized later. |
* |
* (ref: added support for up to 32mb, 17Apr92) -- Rik Faith |
* (ref: update, 25Sept92) -- croutons@crunchy.uucp |
* (ref: 92.10.11 - Linus Torvalds. Corrected 16M limit - no upper memory limit) |
*/ |
ALIGN |
setup_paging: |
movl $1024*2,%ecx /* 2 pages - swapper_pg_dir+1 page table */ |
xorl %eax,%eax |
movl $ SYMBOL_NAME(swapper_pg_dir),%edi /* swapper_pg_dir is at 0x1000 */ |
cld;rep;stosl |
/* Identity-map the kernel in low 4MB memory for ease of transition */ |
/* set present bit/user r/w */ |
movl $ SYMBOL_NAME(pg0)+7,SYMBOL_NAME(swapper_pg_dir) |
/* But the real place is at PAGE_OFFSET */ |
/* set present bit/user r/w */ |
movl $ SYMBOL_NAME(pg0)+7,SYMBOL_NAME(swapper_pg_dir)+__USER_PGD_PTRS*4 |
movl $ SYMBOL_NAME(pg0)+4092,%edi |
movl $0x03ff007,%eax /* 4Mb - 4096 + 7 (r/w user,p) */ |
std |
1: stosl /* fill the page backwards - more efficient :-) */ |
subl $0x1000,%eax |
jge 1b |
cld |
movl $ SYMBOL_NAME(swapper_pg_dir),%eax |
movl %eax,%cr3 /* cr3 - page directory start */ |
movl %cr0,%eax |
orl $0x80000000,%eax |
movl %eax,%cr0 /* set paging (PG) bit */ |
ret /* this also flushes the prefetch-queue */ |
|
/* |
* page 0 is made non-existent, so that kernel NULL pointer references get |
* caught. Thus the swapper page directory has been moved to 0x1000 |
* |
* XXX Actually, the swapper page directory is at 0x1000 plus 1 megabyte, |
* with the introduction of the compressed boot code. Theoretically, |
* the original design of overlaying the startup code with the swapper |
* page directory is still possible --- it would reduce the size of the kernel |
* by 2-3k. This would be a good thing to do at some point..... |
*/ |
.org 0x1000 |
ENTRY(swapper_pg_dir) |
/* |
* The page tables are initialized to only 4MB here - the final page |
* tables are set up later depending on memory size. |
*/ |
.org 0x2000 |
ENTRY(pg0) |
|
.org 0x3000 |
ENTRY(empty_bad_page) |
|
.org 0x4000 |
ENTRY(empty_bad_page_table) |
|
.org 0x5000 |
ENTRY(empty_zero_page) |
|
.org 0x6000 |
|
stack_start: |
.long SYMBOL_NAME(init_user_stack)+4096 |
.long KERNEL_DS |
|
/* NOTE: keep the idt short behind the above '.org 0x6000' |
It must fit completely within _one_ page */ |
ENTRY(idt) |
.fill 256,8,0 # idt is uninitialized |
|
/* This is the default interrupt "handler" :-) */ |
int_msg: |
.asciz "Unknown interrupt\n" |
ALIGN |
ignore_int: |
cld |
pushl %eax |
pushl %ecx |
pushl %edx |
push %ds |
push %es |
push %fs |
movl $(KERNEL_DS),%eax |
mov %ax,%ds |
mov %ax,%es |
mov %ax,%fs |
pushl $int_msg |
call SYMBOL_NAME(printk) |
popl %eax |
pop %fs |
pop %es |
pop %ds |
popl %edx |
popl %ecx |
popl %eax |
iret |
|
/* |
* The interrupt descriptor table has room for 256 idt's |
*/ |
ALIGN |
.word 0 |
idt_descr: |
.word 256*8-1 # idt contains 256 entries |
.long __PAGE_OFFSET+SYMBOL_NAME(idt) |
|
ALIGN |
.word 0 |
gdt_descr: |
#ifdef CONFIG_APM |
.word (11+2*NR_TASKS)*8-1 |
#else |
.word (8+2*NR_TASKS)*8-1 |
#endif |
.long __PAGE_OFFSET+SYMBOL_NAME(gdt) |
|
/* |
* This gdt setup gives the kernel a 1GB address space at virtual |
* address PAGE_OFFSET - space enough for expansion, I hope. |
*/ |
|
#define upper_seg(type,dpl,base,limit) \ |
((base) & 0xff000000) | \ |
(((base) & 0x00ff0000)>>16) | \ |
(((limit)>>12) & 0xf0000) | \ |
((dpl)<<13) | \ |
(0x00c09000) | \ |
((type)<<8) |
|
#define lower_seg(type,dpl,base,limit) \ |
(((base) & 0x0000ffff)<<16) | \ |
(((limit)>>12) & 0x0ffff) |
|
#define x86_seg(type,dpl,base,limit) \ |
.long lower_seg(type,dpl,base,limit); \ |
.long upper_seg(type,dpl,base,limit) |
|
ENTRY(gdt) |
.quad 0x0000000000000000 /* NULL descriptor */ |
.quad 0x0000000000000000 /* not used */ |
|
/* 0x10 kernel 1GB code at 0xC0000000: */ |
x86_seg(0xa,0,__PAGE_OFFSET,0xffffffff-__PAGE_OFFSET) |
|
/* 0x18 kernel 1GB data at 0xC0000000: */ |
x86_seg(0x2,0,__PAGE_OFFSET,0xffffffff-__PAGE_OFFSET) |
|
/* 0x23 user 3GB code at 0x00000000: */ |
x86_seg(0xa,3,0,__PAGE_OFFSET-1) |
|
/* 0x2b user 3GB data at 0x00000000: */ |
x86_seg(0x2,3,0,__PAGE_OFFSET-1) |
|
.quad 0x0000000000000000 /* not used */ |
.quad 0x0000000000000000 /* not used */ |
.fill 2*NR_TASKS,8,0 /* space for LDT's and TSS's etc */ |
#ifdef CONFIG_APM |
.quad 0x00c09a0000000000 /* APM CS code */ |
.quad 0x00809a0000000000 /* APM CS 16 code (16 bit) */ |
.quad 0x00c0920000000000 /* APM DS data */ |
#endif |
/ldt.c
0,0 → 1,136
/* |
* linux/kernel/ldt.c |
* |
* Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds |
*/ |
|
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/string.h> |
#include <linux/mm.h> |
#include <asm/segment.h> |
#include <asm/system.h> |
#include <linux/ldt.h> |
#include <asm/ptrace.h> |
|
static int read_ldt(void * ptr, unsigned long bytecount) |
{ |
int error; |
void * address = current->ldt; |
unsigned long size; |
|
if (!ptr) |
return -EINVAL; |
size = LDT_ENTRIES*LDT_ENTRY_SIZE; |
if (!address) { |
address = &default_ldt; |
size = sizeof(default_ldt); |
} |
if (size > bytecount) |
size = bytecount; |
error = verify_area(VERIFY_WRITE, ptr, size); |
if (error) |
return error; |
memcpy_tofs(ptr, address, size); |
return size; |
} |
|
static inline int limits_ok(struct modify_ldt_ldt_s *ldt_info) |
{ |
unsigned long base, limit; |
/* linear address of first and last accessible byte */ |
unsigned long first, last; |
|
base = ldt_info->base_addr; |
limit = ldt_info->limit; |
if (ldt_info->limit_in_pages) |
limit = limit * PAGE_SIZE + PAGE_SIZE - 1; |
|
first = base; |
last = limit + base; |
|
/* segment grows down? */ |
if (ldt_info->contents == 1) { |
/* data segment grows down */ |
first = base+limit+1; |
last = base+65535; |
if (ldt_info->seg_32bit) |
last = base-1; |
} |
return (last >= first && last < TASK_SIZE); |
} |
|
static int write_ldt(struct pt_regs * regs, void * ptr, unsigned long bytecount, int oldmode) |
{ |
struct modify_ldt_ldt_s ldt_info; |
unsigned long *lp; |
int error, i; |
|
if (bytecount != sizeof(ldt_info)) |
return -EINVAL; |
error = verify_area(VERIFY_READ, ptr, sizeof(ldt_info)); |
if (error) |
return error; |
|
memcpy_fromfs(&ldt_info, ptr, sizeof(ldt_info)); |
|
if ((ldt_info.contents == 3 && (oldmode || ldt_info.seg_not_present == 0)) || ldt_info.entry_number >= LDT_ENTRIES) |
return -EINVAL; |
|
if (!limits_ok(&ldt_info) && (oldmode || ldt_info.seg_not_present == 0)) |
return -EINVAL; |
|
if (!current->ldt) { |
for (i=1 ; i<NR_TASKS ; i++) { |
if (task[i] == current) { |
if (!(current->ldt = (struct desc_struct*) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE))) |
return -ENOMEM; |
memset(current->ldt, 0, LDT_ENTRIES*LDT_ENTRY_SIZE); |
set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, current->ldt, LDT_ENTRIES); |
load_ldt(i); |
} |
} |
} |
|
lp = (unsigned long *) ¤t->ldt[ldt_info.entry_number]; |
/* Allow LDTs to be cleared by the user. */ |
if (ldt_info.base_addr == 0 && ldt_info.limit == 0 |
&& (oldmode || |
( ldt_info.contents == 0 |
&& ldt_info.read_exec_only == 1 |
&& ldt_info.seg_32bit == 0 |
&& ldt_info.limit_in_pages == 0 |
&& ldt_info.seg_not_present == 1 |
&& ldt_info.useable == 0 )) ) { |
unsigned short sel =(ldt_info.entry_number <<3) | 7; |
if (regs->fs == sel || regs->gs == sel) |
return -EBUSY; |
*lp = 0; |
*(lp+1) = 0; |
return 0; |
} |
*lp = ((ldt_info.base_addr & 0x0000ffff) << 16) | |
(ldt_info.limit & 0x0ffff); |
*(lp+1) = (ldt_info.base_addr & 0xff000000) | |
((ldt_info.base_addr & 0x00ff0000)>>16) | |
(ldt_info.limit & 0xf0000) | |
(ldt_info.contents << 10) | |
((ldt_info.read_exec_only ^ 1) << 9) | |
(ldt_info.seg_32bit << 22) | |
(ldt_info.limit_in_pages << 23) | |
((ldt_info.seg_not_present ^1) << 15) | |
0x7000; |
if (!oldmode) *(lp+1) |= (ldt_info.useable << 20); |
return 0; |
} |
|
asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount) |
{ |
if (func == 0) |
return read_ldt(ptr, bytecount); |
if (func == 1) |
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 1); |
if (func == 0x11) |
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 0); |
return -ENOSYS; |
} |
/mtrr.c
0,0 → 1,602
/* |
* Read and write Memory Type Range Registers (MTRRs) |
* |
* These machine specific registers contain information about |
* caching of memory regions on Intel processors. |
* |
* This code has been derived from pform_mod.c by M. Tisch"auser |
* (email martin@ikcbarka.fzk.de). Special thanks to mingo for |
* his hint. |
* |
* (c) 1997 M. Ohlenroth <moh@informatik.tu-chemnitz.de> |
* NO WARRANTY: use this code at your own risk! |
* |
* This code is released under the GNU public license version 2 or |
* later. |
* |
* modified to have a /proc/mtrr interface by M. Fr"ohlich, Jan. 1998 |
* <frohlich@na.uni-tuebingen.de> |
* the user Interface is partly taken form mtrr-patch-v1.5 |
* Richard Gooch may be reached by email at rgooch@atnf.csiro.au |
* The postal address is: |
* Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia. |
* |
*/ |
|
#include <linux/kernel.h> |
#include <linux/ctype.h> |
#include <linux/proc_fs.h> |
#include <linux/smp.h> |
#include <linux/interrupt.h> |
#include <asm/system.h> |
#include <asm/processor.h> |
#include <asm/mtrr.h> |
|
#define MTRR_CAP 0x0fe /* MTRRcap register */ |
#define MTRR_VARIABLE 0x200 /* variable length registers */ |
#define MTRR_FIXED64K 0x250 /* fixed size registers 64k */ |
#define MTRR_FIXED16K 0x258 /* fixed size registers 16K */ |
#define MTRR_FIXED4K 0x268 /* fixed size registers 4K */ |
#define MTRR_DEFTYPE 0x2ff /* MTRRdefType register */ |
|
/* |
* data type for the MTRRcap register |
*/ |
typedef struct { |
__u64 VCNT : 8 __attribute__ ((packed)), |
FIX : 1 __attribute__ ((packed)), |
__reserved_2 : 1 __attribute__ ((packed)), |
WC : 1 __attribute__ ((packed)), |
__reserved_1 : 53 __attribute__ ((packed)); |
} MTRRcap_t __attribute__ ((packed)); |
|
|
/* |
* data type for the MTRRdefType register |
*/ |
typedef struct { |
__u64 Type : 8 __attribute__ ((packed)), |
__reserved_1 : 2 __attribute__ ((packed)), |
FE : 1 __attribute__ ((packed)), |
E : 1 __attribute__ ((packed)), |
__reserved_2 : 52 __attribute__ ((packed)); |
} MTRRdefType_t __attribute__ ((packed)); |
|
/* FIXME implement the entry struct */ |
typedef struct MTRRfix64K_t { |
__u64 raw; |
} MTRRfix64K_t __attribute__ ((packed)); |
|
typedef struct MTRRfix16K_t { |
__u64 raw; |
} MTRRfix16K_t __attribute__ ((packed)); |
|
typedef struct MTRRfix4K_t { |
__u64 raw; |
} MTRRfix4K_t __attribute__ ((packed)); |
|
/* |
* data type for a pair of variable MTRR registers |
*/ |
typedef struct { |
struct { |
__u64 Type : 8 __attribute__ ((packed)), |
__reserved_1 : 4 __attribute__ ((packed)), |
PhysBase : 24 __attribute__ ((packed)), |
__reserved_2 : 28 __attribute__ ((packed)); |
} MTRRphysBase __attribute__ ((packed)); |
|
struct { |
__u64 __reserved_3 : 11 __attribute__ ((packed)), |
V : 1 __attribute__ ((packed)), |
PhysMask : 24 __attribute__ ((packed)), |
__reserved_4 : 28 __attribute__ ((packed)); |
} MTRRphysMask __attribute__ ((packed)); |
} MTRRvar_t __attribute__ ((packed)); |
|
#define RAW_ACCESS64(data) (*(unsigned long long *)(&(data))) |
|
/* |
* MTRR configuration struct |
*/ |
struct mtrr_cntl_t { |
MTRRcap_t MTRRcap; /* MTRR capability register */ |
MTRRdefType_t MTRRdefType; /* MTRR default type register */ |
MTRRfix64K_t fixed64; /* fixed length entries (raw data) */ |
MTRRfix16K_t fixed16[2]; |
MTRRfix4K_t fixed4[8]; |
MTRRvar_t variable[0]; /* variable type entries */ |
}; |
|
static struct mtrr_cntl_t *mtrrcntl = NULL; |
|
/* |
* Deletes the variable MTRR *MTRRvar |
*/ |
static inline void MTRRvar_delete(MTRRvar_t *MTRRvar) |
{ |
RAW_ACCESS64(MTRRvar->MTRRphysBase) = 0; |
RAW_ACCESS64(MTRRvar->MTRRphysMask) = 0; |
} |
|
|
/* |
* Sets the variable MTRR *MTRRvar |
*/ |
static inline void MTRRvar_set(MTRRvar_t *MTRRvar, unsigned int type, |
unsigned long base, unsigned long size) |
{ |
unsigned long val; |
base >>= 12; |
size >>= 12; |
|
MTRRvar->MTRRphysBase.Type = type; |
MTRRvar->MTRRphysBase.PhysBase = base; |
|
MTRRvar->MTRRphysMask.V = 1; |
val = 1<<25; |
while (0 == (val & size)) val |= (val>>1); |
MTRRvar->MTRRphysMask.PhysMask = val; |
} |
|
|
/* |
* returns 1 if the variable MTRR entry *MTRRvar is valid, 0 otherwise |
*/ |
static inline int MTRRvar_is_valid(const MTRRvar_t *MTRRvar) |
{ |
return MTRRvar->MTRRphysMask.V; |
} |
|
/* |
* returns the type of the variable MTRR entry *MTRRvar |
*/ |
static inline int MTRRvar_get_type(const MTRRvar_t *MTRRvar) |
{ |
return MTRRvar->MTRRphysBase.Type; |
} |
|
/* |
* returns the base of the variable MTRR entry *MTRRvar |
*/ |
static inline unsigned long long MTRRvar_get_base(const MTRRvar_t *MTRRvar) |
{ |
return ((unsigned long long)MTRRvar->MTRRphysBase.PhysBase) << 12; |
} |
|
/* |
* returns the size of the variable MTRR entry *MTRRvar |
*/ |
static inline unsigned long long MTRRvar_get_size(const MTRRvar_t *MTRRvar) |
{ |
if (MTRRvar->MTRRphysMask.PhysMask == 0) { |
return 0; |
} else { |
unsigned long size = 1; |
const unsigned long Mask = MTRRvar->MTRRphysMask.PhysMask; |
while (0 == (Mask & size)) size <<= 1; |
return ((unsigned long long)size) << 12; |
} |
} |
|
/* |
* returns the eflags register |
*/ |
static inline int read_eflags(void) |
{ |
int ret; |
asm volatile ( |
"pushfl\n\t" |
"popl %%eax\n\t" |
:"=a" (ret) |
: |
); |
return ret; |
} |
|
/* |
* writes the eflags register |
*/ |
static inline void write_eflags(int flag) |
{ |
asm volatile ( |
"pushl %%eax\n\t" |
"popfl\n\t" |
: |
:"a" (flag) |
); |
} |
|
/* |
* returns 1 if the mtrr's are supported by the current processor, 0 otherwise |
*/ |
static inline int mtrr_detect(void) { |
unsigned long flags; |
int eflags; |
int val; |
|
#define MSR_MASK 0x20 |
#define MTRR_MASK 0x1000 |
#define CPUID_MASK 0x200000 |
/* this function may be called before the cpu_data array has |
been initialized */ |
save_flags(flags); sti(); |
eflags = read_eflags(); |
write_eflags(eflags ^ CPUID_MASK); |
if (!((eflags ^ read_eflags()) & CPUID_MASK)) { |
write_eflags(eflags); |
restore_flags(flags); |
return 0; |
} |
write_eflags(eflags); |
restore_flags(flags); |
|
/* get the cpuid level */ |
asm volatile ( |
"xorl %%eax,%%eax\n\t" |
"cpuid" |
:"=a"(val)::"ebx","ecx","edx" |
); |
if (val < 1) return 0; |
/* get the x86_capability value */ |
asm volatile ( |
"movl $1,%%eax\n\t" |
"cpuid" |
:"=d"(val)::"ebx","ecx","eax" |
); |
if (!(val & MSR_MASK)) return 0; |
if (!(val & MTRR_MASK)) return 0; |
|
return 1; |
#undef MSR_MASK |
#undef MTRR_MASK |
#undef CPUID_MASK |
} |
|
|
/* |
* reads the mtrr configuration of the actual processor and returns |
* this configuration on sucess. returns NULL if an error occured or |
* if mtrr's are not supported. |
*/ |
static struct mtrr_cntl_t *read_mtrr_configuration (void) { |
struct mtrr_cntl_t *mtrrcntl; |
int i; |
size_t size; |
MTRRcap_t MTRRcap; |
|
if (!mtrr_detect()) { |
printk("/proc/mtrr: MTRR's are NOT supported\n"); |
return NULL; |
} |
|
RAW_ACCESS64(MTRRcap) = rdmsr(MTRR_CAP); |
|
/* #define DUMP_MTRR */ |
#ifdef DUMP_MTRR |
{ |
/* Written for a bugreport to Gigabyte ... */ |
inline void print_msr(int num) { |
unsigned long long tmp = rdmsr(num); |
printk("MSR #%#06x: 0x%08lx%08lx\n", |
num , (unsigned long)(tmp >> 32), |
(unsigned long)tmp); |
} |
|
print_msr(MTRR_CAP); |
/* all variable type */ |
for (i=0;i < MTRRcap.VCNT;i++) { |
print_msr(MTRR_VARIABLE+2*i); |
print_msr(MTRR_VARIABLE+2*i+1); |
} |
/* all fixed type */ |
print_msr(MTRR_FIXED64K); |
print_msr(MTRR_FIXED16K); |
print_msr(MTRR_FIXED16K+1); |
for (i=0;i<8;i++) |
print_msr(MTRR_FIXED4K+i); |
print_msr(MTRR_DEFTYPE); |
} |
#endif |
|
size = sizeof(struct mtrr_cntl_t) + sizeof(MTRRvar_t)*MTRRcap.VCNT; |
if (NULL == (mtrrcntl = kmalloc(size, GFP_KERNEL))) return NULL; |
memset(mtrrcntl, 0, size); |
|
/* read MTRRcap register */ |
mtrrcntl->MTRRcap = MTRRcap; |
|
/* read MTRRdefType register */ |
RAW_ACCESS64(mtrrcntl->MTRRdefType) = rdmsr(MTRR_DEFTYPE); |
|
/* read fixed length entries */ |
if (mtrrcntl->MTRRdefType.E && mtrrcntl->MTRRdefType.FE) { |
mtrrcntl->fixed64.raw = rdmsr(MTRR_FIXED64K); |
mtrrcntl->fixed16[0].raw = rdmsr(MTRR_FIXED16K); |
mtrrcntl->fixed16[1].raw = rdmsr(MTRR_FIXED16K+1); |
for (i=0;i<8;i++) |
mtrrcntl->fixed4[i].raw = rdmsr(MTRR_FIXED4K+i); |
} |
|
/* read variable length entries */ |
if (mtrrcntl->MTRRdefType.E) { |
const int vcnt = mtrrcntl->MTRRcap.VCNT; |
for (i = 0 ; i < vcnt ; i++) { |
RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysBase) = |
rdmsr(MTRR_VARIABLE + 2*i); |
RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysMask) = |
rdmsr(MTRR_VARIABLE + 2*i + 1); |
} |
} |
|
return mtrrcntl; |
} |
|
/* |
* initializes the global mtrr configuration |
*/ |
/*__init_function(void init_mtrr_config(void)) FIXME*/ |
void init_mtrr_config(void) |
{ |
mtrrcntl = read_mtrr_configuration(); |
} |
|
|
/* write back and invalidate cache */ |
static inline void wbinvd(void) |
{ |
asm volatile("wbinvd"); |
} |
|
/* flush tlb's */ |
static inline void flush__tlb(void) |
{ |
asm volatile ( |
"movl %%cr3, %%eax\n\t" |
"movl %%eax, %%cr3\n\t" |
: |
: |
: "memory", "eax"); |
} |
|
/* clear page global enable and return previous value */ |
static inline unsigned long clear_pge(void) |
{ |
unsigned long ret; |
asm volatile ( |
"movl %%cr4, %%eax\n\t" |
"movl %%eax, %%edx\n\t" |
"andl $0x7f, %%edx\n\t" |
"movl %%edx, %%cr4\n\t" |
: "=a" (ret) |
: |
: "memory", "cc", "eax", "edx"); |
return ret; |
} |
|
/* restores page global enable bit */ |
static inline void restore_pge(unsigned long cr4) |
{ |
asm volatile ( |
"movl %0, %%cr4\n\t" |
: |
: "r" (cr4) |
: "memory"); |
} |
|
/* ... */ |
static inline void disable_cache(void) |
{ |
asm volatile ( |
"movl %%cr0, %%eax\n\t" |
"orl $0x40000000, %%eax\n\t" |
"movl %%eax, %%cr0\n\t" |
: |
: |
:"memory", "cc", "eax"); |
} |
|
/* ... */ |
static inline void enable_cache(void) |
{ |
asm volatile ( |
"movl %%cr0, %%eax\n\t" |
"andl $0xbfffffff, %%eax\n\t" |
"movl %%eax, %%cr0" |
: |
: |
:"memory", "cc", "eax"); |
} |
|
/* clear the MTRRdefType.E and MTRRdefType.FE flag to disable these MTRR's */ |
static inline void disable_mtrr(void) |
{ |
MTRRdefType_t MTRRdefType; |
|
RAW_ACCESS64(MTRRdefType) = rdmsr(MTRR_DEFTYPE); |
MTRRdefType.E = 0; |
MTRRdefType.FE = 0; |
wrmsr(MTRR_DEFTYPE, RAW_ACCESS64(MTRRdefType)); |
} |
|
/* |
* written from pseudocode from intel |
* (PentiumPro Family Developers manual Volume 3, P 322) |
* |
*/ |
static inline unsigned long pre_mtrr_change(void) |
{ |
unsigned long cr4; |
|
cr4 = clear_pge(); |
|
wbinvd(); |
|
disable_cache(); |
|
wbinvd(); |
|
flush__tlb(); |
|
disable_mtrr(); |
|
return cr4; |
} |
|
/* |
* written from pseudocode from intel |
* (PentiumPro Family Developers manual Volume 3, P 322) |
*/ |
static inline void post_mtrr_change(MTRRdefType_t MTRRdefType,unsigned long cr4) |
{ |
wbinvd(); |
|
flush__tlb(); |
|
wrmsr(MTRR_DEFTYPE, RAW_ACCESS64(MTRRdefType)); |
|
enable_cache(); |
|
restore_pge(cr4); |
} |
|
/* |
* writes all fixed mtrr's |
*/ |
static inline void set_mtrr_fixed(void) { |
int i; |
|
wrmsr(MTRR_FIXED64K,mtrrcntl->fixed64.raw); |
wrmsr(MTRR_FIXED16K+0,mtrrcntl->fixed16[0].raw); |
wrmsr(MTRR_FIXED16K+1,mtrrcntl->fixed16[1].raw); |
for (i=0;i<8;i++) |
wrmsr(MTRR_FIXED4K+i,mtrrcntl->fixed4[i].raw); |
} |
|
/* |
* writes all variable mtrr's |
*/ |
static inline void set_mtrr_variable(void) { |
int i; |
const int vcnt = mtrrcntl->MTRRcap.VCNT; |
|
for (i = 0 ; i < vcnt ; i++ ) { |
wrmsr(MTRR_VARIABLE +2*i, |
RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysBase)); |
wrmsr(MTRR_VARIABLE +2*i+1, |
RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysMask)); |
} |
} |
|
|
/* |
* compares the mtrr_cntl_t structure second with that set by |
* the boot processor, |
* returns 0 if equal, |
* -1 if the structs are not initialized, |
* 1 if they are different |
* |
* the *second struct is assumed to be local, it is not locked! |
*/ |
static inline int compare_mtrr_configuration(struct mtrr_cntl_t *second) { |
int i, result = 0; |
|
if (NULL == mtrrcntl) { result = -1; goto end; } |
if (NULL == second) { result = -1; goto end; } |
if (RAW_ACCESS64(mtrrcntl->MTRRcap) |
!= RAW_ACCESS64(second->MTRRcap)) { |
result = 1; goto end; |
} |
|
if (RAW_ACCESS64(mtrrcntl->MTRRdefType) |
!= RAW_ACCESS64(second->MTRRdefType)) { |
result = 1; goto end; |
} |
|
if (mtrrcntl->fixed64.raw != second->fixed64.raw) { |
result = 1; goto end; |
} |
if (mtrrcntl->fixed16[0].raw != second->fixed16[0].raw) { |
result = 1; goto end; |
} |
if (mtrrcntl->fixed16[1].raw != second->fixed16[1].raw) { |
result = 1; goto end; |
} |
|
for (i=0;i<8;i++) |
if (mtrrcntl->fixed4[i].raw != second->fixed4[i].raw) { |
result = 1; goto end; |
} |
{ |
const int vcnt = mtrrcntl->MTRRcap.VCNT; |
for (i = 0; i < vcnt; i++) { |
if (RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysBase) != |
RAW_ACCESS64(second->variable[i].MTRRphysBase)) { |
result = 1; goto end; |
} |
if (RAW_ACCESS64(mtrrcntl->variable[i].MTRRphysMask) != |
RAW_ACCESS64(second->variable[i].MTRRphysMask)) { |
result = 1; goto end; |
} |
} |
} |
|
end: |
|
return result; |
} |
|
/* |
* compares the mtrr configuration of the current processor with the |
* main configuration and overwrites the mtrr's in the processor if they |
* differ. (fixes a bug in the GA-686DX mainboard BIOS) |
*/ |
void check_mtrr_config(void) { |
unsigned long cr4; |
unsigned long flags; |
struct mtrr_cntl_t *this_cpu_setting; |
int result; |
|
save_flags(flags); sti(); |
|
/* if global struct is not initialized return */ |
if (mtrrcntl == NULL) { |
restore_flags(flags); |
return; |
} |
|
/* disable MTRR feature if this_cpu_setting == NULL */ |
/* read mtrr configuration of this cpu */ |
this_cpu_setting = read_mtrr_configuration(); |
if (this_cpu_setting == NULL) { |
printk("/proc/mtrr: MTRR's are NOT supported by cpu %i.\n", |
smp_processor_id()); |
restore_flags(flags); |
|
return; |
} |
/* compare mtrr configuration */ |
result = compare_mtrr_configuration(this_cpu_setting); |
kfree(this_cpu_setting); |
/* return if mtrr setting is correct */ |
if (0 >= result) { |
restore_flags(flags); |
return; |
} |
|
/* prepare cpu's for setting mtrr's */ |
cr4 = pre_mtrr_change(); |
|
/* set all mtrr's */ |
set_mtrr_fixed(); |
set_mtrr_variable(); |
|
/* prepare cpu's for running */ |
post_mtrr_change(mtrrcntl->MTRRdefType, cr4); |
|
restore_flags(flags); |
|
printk("\nBIOS bug workaround: MTRR configuration changed on cpu " |
"%i.\n", smp_processor_id()); |
} |
|
/sys_i386.c
0,0 → 1,176
/* |
* linux/arch/i386/kernel/sys_i386.c |
* |
* This file contains various random system calls that |
* have a non-standard calling sequence on the Linux/i386 |
* platform. |
*/ |
|
#include <linux/errno.h> |
#include <linux/sched.h> |
#include <linux/mm.h> |
#include <linux/sem.h> |
#include <linux/msg.h> |
#include <linux/shm.h> |
#include <linux/stat.h> |
#include <linux/mman.h> |
|
#include <asm/segment.h> |
|
/* |
* sys_pipe() is the normal C calling standard for creating |
* a pipe. It's not the way unix traditionally does this, though. |
*/ |
asmlinkage int sys_pipe(unsigned long * fildes) |
{ |
int fd[2]; |
int error; |
|
error = verify_area(VERIFY_WRITE,fildes,8); |
if (error) |
return error; |
error = do_pipe(fd); |
if (error) |
return error; |
put_fs_long(fd[0],0+fildes); |
put_fs_long(fd[1],1+fildes); |
return 0; |
} |
|
/* |
* Perform the select(nd, in, out, ex, tv) and mmap() system |
* calls. Linux/i386 didn't use to be able to handle more than |
* 4 system call parameters, so these system calls used a memory |
* block for parameter passing.. |
*/ |
asmlinkage int old_mmap(unsigned long *buffer) |
{ |
int error; |
unsigned long flags; |
struct file * file = NULL; |
|
error = verify_area(VERIFY_READ, buffer, 6*sizeof(long)); |
if (error) |
return error; |
flags = get_user(buffer+3); |
if (!(flags & MAP_ANONYMOUS)) { |
unsigned long fd = get_user(buffer+4); |
if (fd >= NR_OPEN || !(file = current->files->fd[fd])) |
return -EBADF; |
} |
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); |
return do_mmap(file, get_user(buffer), get_user(buffer+1), |
get_user(buffer+2), flags, get_user(buffer+5)); |
} |
|
extern asmlinkage int sys_select(int, fd_set *, fd_set *, fd_set *, struct timeval *); |
|
asmlinkage int old_select(unsigned long *buffer) |
{ |
int n; |
fd_set *inp; |
fd_set *outp; |
fd_set *exp; |
struct timeval *tvp; |
|
n = verify_area(VERIFY_READ, buffer, 5*sizeof(unsigned long)); |
if (n) |
return n; |
n = get_user(buffer); |
inp = (fd_set *) get_user(buffer+1); |
outp = (fd_set *) get_user(buffer+2); |
exp = (fd_set *) get_user(buffer+3); |
tvp = (struct timeval *) get_user(buffer+4); |
return sys_select(n, inp, outp, exp, tvp); |
} |
|
/* |
* sys_ipc() is the de-multiplexer for the SysV IPC calls.. |
* |
* This is really horribly ugly. |
*/ |
asmlinkage int sys_ipc (uint call, int first, int second, int third, void *ptr, long fifth) |
{ |
int version; |
|
version = call >> 16; /* hack for backward compatibility */ |
call &= 0xffff; |
|
if (call <= SEMCTL) |
switch (call) { |
case SEMOP: |
return sys_semop (first, (struct sembuf *)ptr, second); |
case SEMGET: |
return sys_semget (first, second, third); |
case SEMCTL: { |
union semun fourth; |
int err; |
if (!ptr) |
return -EINVAL; |
if ((err = verify_area (VERIFY_READ, ptr, sizeof(long)))) |
return err; |
fourth.__pad = (void *) get_fs_long(ptr); |
return sys_semctl (first, second, third, fourth); |
} |
default: |
return -EINVAL; |
} |
if (call <= MSGCTL) |
switch (call) { |
case MSGSND: |
return sys_msgsnd (first, (struct msgbuf *) ptr, |
second, third); |
case MSGRCV: |
switch (version) { |
case 0: { |
struct ipc_kludge tmp; |
int err; |
if (!ptr) |
return -EINVAL; |
if ((err = verify_area (VERIFY_READ, ptr, sizeof(tmp)))) |
return err; |
memcpy_fromfs (&tmp,(struct ipc_kludge *) ptr, |
sizeof (tmp)); |
return sys_msgrcv (first, tmp.msgp, second, tmp.msgtyp, third); |
} |
case 1: default: |
return sys_msgrcv (first, (struct msgbuf *) ptr, second, fifth, third); |
} |
case MSGGET: |
return sys_msgget ((key_t) first, second); |
case MSGCTL: |
return sys_msgctl (first, second, (struct msqid_ds *) ptr); |
default: |
return -EINVAL; |
} |
if (call <= SHMCTL) |
switch (call) { |
case SHMAT: |
switch (version) { |
case 0: default: { |
ulong raddr; |
int err; |
if ((err = verify_area(VERIFY_WRITE, (ulong*) third, sizeof(ulong)))) |
return err; |
err = sys_shmat (first, (char *) ptr, second, &raddr); |
if (err) |
return err; |
put_fs_long (raddr, (ulong *) third); |
return 0; |
} |
case 1: /* iBCS2 emulator entry point */ |
if (get_fs() != get_ds()) |
return -EINVAL; |
return sys_shmat (first, (char *) ptr, second, (ulong *) third); |
} |
case SHMDT: |
return sys_shmdt ((char *)ptr); |
case SHMGET: |
return sys_shmget (first, second, third); |
case SHMCTL: |
return sys_shmctl (first, second, (struct shmid_ds *) ptr); |
default: |
return -EINVAL; |
} |
return -EINVAL; |
} |
/traps.c
0,0 → 1,460
/* |
* linux/arch/i386/traps.c |
* |
* Copyright (C) 1991, 1992 Linus Torvalds |
*/ |
|
/* |
* 'Traps.c' handles hardware traps and faults after we have saved some |
* state in 'asm.s'. Currently mostly a debugging-aid, will be extended |
* to mainly kill the offending process (probably by giving it a signal, |
* but possibly by killing it outright if necessary). |
*/ |
#include <linux/config.h> |
#include <linux/head.h> |
#include <linux/sched.h> |
#include <linux/kernel.h> |
#include <linux/string.h> |
#include <linux/errno.h> |
#include <linux/ptrace.h> |
#include <linux/config.h> |
#include <linux/timer.h> |
#include <linux/mm.h> |
|
#include <asm/system.h> |
#include <asm/segment.h> |
#include <asm/io.h> |
#include <asm/pgtable.h> |
|
asmlinkage int system_call(void); |
asmlinkage void lcall7(void); |
struct desc_struct default_ldt = { 0, 0 }; |
|
static inline void console_verbose(void) |
{ |
extern int console_loglevel; |
console_loglevel = 15; |
} |
|
#define DO_ERROR(trapnr, signr, str, name, tsk) \ |
asmlinkage void do_##name(struct pt_regs * regs, long error_code) \ |
{ \ |
tsk->tss.error_code = error_code; \ |
tsk->tss.trap_no = trapnr; \ |
force_sig(signr, tsk); \ |
die_if_kernel(str,regs,error_code); \ |
} |
|
#define DO_VM86_ERROR(trapnr, signr, str, name, tsk) \ |
asmlinkage void do_##name(struct pt_regs * regs, long error_code) \ |
{ \ |
if (regs->eflags & VM_MASK) { \ |
if (!handle_vm86_trap((struct vm86_regs *) regs, error_code, trapnr)) \ |
return; \ |
/* else fall through */ \ |
} \ |
tsk->tss.error_code = error_code; \ |
tsk->tss.trap_no = trapnr; \ |
force_sig(signr, tsk); \ |
die_if_kernel(str,regs,error_code); \ |
} |
|
#define get_seg_byte(seg,addr) ({ \ |
register unsigned char __res; \ |
__asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \ |
:"=a" (__res):"0" (seg),"m" (*(addr))); \ |
__res;}) |
|
#define get_seg_long(seg,addr) ({ \ |
register unsigned long __res; \ |
__asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \ |
:"=a" (__res):"0" (seg),"m" (*(addr))); \ |
__res;}) |
|
#define _fs() ({ \ |
register unsigned short __res; \ |
__asm__("mov %%fs,%%ax":"=a" (__res):); \ |
__res;}) |
|
void page_exception(void); |
|
asmlinkage void divide_error(void); |
asmlinkage void debug(void); |
asmlinkage void nmi(void); |
asmlinkage void int3(void); |
asmlinkage void overflow(void); |
asmlinkage void bounds(void); |
asmlinkage void invalid_op(void); |
asmlinkage void device_not_available(void); |
asmlinkage void double_fault(void); |
asmlinkage void coprocessor_segment_overrun(void); |
asmlinkage void invalid_TSS(void); |
asmlinkage void segment_not_present(void); |
asmlinkage void stack_segment(void); |
asmlinkage void general_protection(void); |
asmlinkage void page_fault(void); |
asmlinkage void coprocessor_error(void); |
asmlinkage void reserved(void); |
asmlinkage void alignment_check(void); |
asmlinkage void spurious_interrupt_bug(void); |
|
int kstack_depth_to_print = 24; |
|
/* |
* These constants are for searching for possible module text |
* segments. VMALLOC_OFFSET comes from mm/vmalloc.c; MODULE_RANGE is |
* a guess of how much space is likely to be vmalloced. |
*/ |
#define VMALLOC_OFFSET (8*1024*1024) |
#define MODULE_RANGE (8*1024*1024) |
|
/*static*/ void die_if_kernel(const char * str, struct pt_regs * regs, long err) |
{ |
int i; |
unsigned long esp; |
unsigned short ss; |
unsigned long *stack, addr, module_start, module_end; |
extern char start_kernel, _etext; |
|
esp = (unsigned long) ®s->esp; |
ss = KERNEL_DS; |
if ((regs->eflags & VM_MASK) || (3 & regs->cs) == 3) |
return; |
if (regs->cs & 3) { |
esp = regs->esp; |
ss = regs->ss; |
} |
console_verbose(); |
printk("%s: %04lx\n", str, err & 0xffff); |
printk("CPU: %d\n", smp_processor_id()); |
printk("EIP: %04x:[<%08lx>]\nEFLAGS: %08lx\n", 0xffff & regs->cs,regs->eip,regs->eflags); |
printk("eax: %08lx ebx: %08lx ecx: %08lx edx: %08lx\n", |
regs->eax, regs->ebx, regs->ecx, regs->edx); |
printk("esi: %08lx edi: %08lx ebp: %08lx esp: %08lx\n", |
regs->esi, regs->edi, regs->ebp, esp); |
printk("ds: %04x es: %04x fs: %04x gs: %04x ss: %04x\n", |
regs->ds, regs->es, regs->fs, regs->gs, ss); |
store_TR(i); |
if (STACK_MAGIC != *(unsigned long *)current->kernel_stack_page) |
printk("Corrupted stack page\n"); |
printk("Process %s (pid: %d, process nr: %d, stackpage=%08lx)\nStack: ", |
current->comm, current->pid, 0xffff & i, current->kernel_stack_page); |
stack = (unsigned long *) esp; |
for(i=0; i < kstack_depth_to_print; i++) { |
if (((long) stack & 4095) == 0) |
break; |
if (i && ((i % 8) == 0)) |
printk("\n "); |
printk("%08lx ", get_seg_long(ss,stack++)); |
} |
printk("\nCall Trace: "); |
stack = (unsigned long *) esp; |
i = 1; |
module_start = ((high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)); |
module_end = module_start + MODULE_RANGE; |
while (((long) stack & 4095) != 0) { |
addr = get_seg_long(ss, stack++); |
/* |
* If the address is either in the text segment of the |
* kernel, or in the region which contains vmalloc'ed |
* memory, it *may* be the address of a calling |
* routine; if so, print it so that someone tracing |
* down the cause of the crash will be able to figure |
* out the call path that was taken. |
*/ |
if (((addr >= (unsigned long) &start_kernel) && |
(addr <= (unsigned long) &_etext)) || |
((addr >= module_start) && (addr <= module_end))) { |
if (i && ((i % 8) == 0)) |
printk("\n "); |
printk("[<%08lx>] ", addr); |
i++; |
} |
} |
printk("\nCode: "); |
for(i=0;i<20;i++) |
printk("%02x ",0xff & get_seg_byte(regs->cs,(i+(char *)regs->eip))); |
printk("\n"); |
do_exit(SIGSEGV); |
} |
|
DO_VM86_ERROR( 0, SIGFPE, "divide error", divide_error, current) |
DO_VM86_ERROR( 3, SIGTRAP, "int3", int3, current) |
DO_VM86_ERROR( 4, SIGSEGV, "overflow", overflow, current) |
DO_VM86_ERROR( 5, SIGSEGV, "bounds", bounds, current) |
DO_ERROR( 6, SIGILL, "invalid operand", invalid_op, current) |
DO_VM86_ERROR( 7, SIGSEGV, "device not available", device_not_available, current) |
DO_ERROR( 8, SIGSEGV, "double fault", double_fault, current) |
DO_ERROR( 9, SIGFPE, "coprocessor segment overrun", coprocessor_segment_overrun, last_task_used_math) |
DO_ERROR(10, SIGSEGV, "invalid TSS", invalid_TSS, current) |
DO_ERROR(11, SIGBUS, "segment not present", segment_not_present, current) |
DO_ERROR(12, SIGBUS, "stack segment", stack_segment, current) |
DO_ERROR(17, SIGSEGV, "alignment check", alignment_check, current) |
DO_ERROR(18, SIGSEGV, "reserved", reserved, current) |
|
/* divide_error is after ret_from_sys_call in entry.S */ |
asmlinkage void ret_from_sys_call(void) __asm__("ret_from_sys_call"); |
asmlinkage void divide_error(void) __asm__("divide_error"); |
|
asmlinkage void do_general_protection(struct pt_regs * regs, long error_code) |
{ |
if (regs->eflags & VM_MASK) { |
handle_vm86_fault((struct vm86_regs *) regs, error_code); |
return; |
} |
|
/* |
* HACK HACK HACK :) Fixing the segment invalid on syscall return |
* barfage for 2.0 has been put into the too-hard basket but having |
* a user producing endless GPFs is unacceptable as well. - Paul G. |
*/ |
if ((regs->cs & 3) != 3) { |
if (regs->eip >= (unsigned long)ret_from_sys_call && |
regs->eip < (unsigned long)divide_error) { |
static int moancount = 0; |
if (moancount < 5) { |
printk(KERN_INFO "Ignoring GPF attempt from program \"%s\" (pid %d).\n", |
current->comm, current->pid); |
moancount++; |
} |
do_exit(SIGSEGV); |
} |
else |
die_if_kernel("general protection",regs,error_code); |
} |
current->tss.error_code = error_code; |
current->tss.trap_no = 13; |
force_sig(SIGSEGV, current); |
} |
|
asmlinkage void do_nmi(struct pt_regs * regs, long error_code) |
{ |
#ifdef CONFIG_SMP_NMI_INVAL |
smp_flush_tlb_rcv(); |
#else |
#ifndef CONFIG_IGNORE_NMI |
printk("Uhhuh. NMI received. Dazed and confused, but trying to continue\n"); |
printk("You probably have a hardware problem with your RAM chips or a\n"); |
printk("power saving mode enabled.\n"); |
#endif |
#endif |
} |
|
asmlinkage void do_debug(struct pt_regs * regs, long error_code) |
{ |
if (regs->eflags & VM_MASK) { |
handle_vm86_trap((struct vm86_regs *) regs, error_code, 1); |
return; |
} |
force_sig(SIGTRAP, current); |
current->tss.trap_no = 1; |
current->tss.error_code = error_code; |
if ((regs->cs & 3) == 0) { |
/* If this is a kernel mode trap, then reset db7 and allow us to continue */ |
__asm__("movl %0,%%db7" |
: /* no output */ |
: "r" (0)); |
return; |
} |
die_if_kernel("debug",regs,error_code); |
} |
|
/* |
* Note that we play around with the 'TS' bit to hopefully get |
* the correct behaviour even in the presence of the asynchronous |
* IRQ13 behaviour |
*/ |
void math_error(void) |
{ |
struct task_struct * task; |
|
clts(); |
#ifdef __SMP__ |
task = current; |
#else |
task = last_task_used_math; |
last_task_used_math = NULL; |
if (!task) { |
__asm__("fnclex"); |
return; |
} |
#endif |
/* |
* Save the info for the exception handler |
*/ |
__asm__ __volatile__("fnsave %0":"=m" (task->tss.i387.hard)); |
task->flags&=~PF_USEDFPU; |
stts(); |
|
force_sig(SIGFPE, task); |
task->tss.trap_no = 16; |
task->tss.error_code = 0; |
} |
|
asmlinkage void do_coprocessor_error(struct pt_regs * regs, long error_code) |
{ |
ignore_irq13 = 1; |
math_error(); |
} |
|
asmlinkage void do_spurious_interrupt_bug(struct pt_regs * regs, |
long error_code) |
{ |
#if 0 |
/* No need to warn about this any longer. */ |
printk("Ignoring P6 Local APIC Spurious Interrupt Bug...\n"); |
#endif |
} |
|
/* |
* 'math_state_restore()' saves the current math information in the |
* old math state array, and gets the new ones from the current task |
* |
* Careful.. There are problems with IBM-designed IRQ13 behaviour. |
* Don't touch unless you *really* know how it works. |
*/ |
asmlinkage void math_state_restore(void) |
{ |
__asm__ __volatile__("clts"); /* Allow maths ops (or we recurse) */ |
|
/* |
* SMP is actually simpler than uniprocessor for once. Because |
* we can't pull the delayed FPU switching trick Linus does |
* we simply have to do the restore each context switch and |
* set the flag. switch_to() will always save the state in |
* case we swap processors. We also don't use the coprocessor |
* timer - IRQ 13 mode isn't used with SMP machines (thank god). |
*/ |
#ifndef __SMP__ |
if (last_task_used_math == current) |
return; |
if (last_task_used_math) |
__asm__("fnsave %0":"=m" (last_task_used_math->tss.i387)); |
else |
__asm__("fnclex"); |
last_task_used_math = current; |
#endif |
|
if(current->used_math) |
__asm__("frstor %0": :"m" (current->tss.i387)); |
else |
{ |
/* |
* Our first FPU usage, clean the chip. |
*/ |
__asm__("fninit"); |
current->used_math = 1; |
} |
current->flags|=PF_USEDFPU; /* So we fnsave on switch_to() */ |
} |
|
#ifndef CONFIG_MATH_EMULATION |
|
asmlinkage void math_emulate(long arg) |
{ |
printk("math-emulation not enabled and no coprocessor found.\n"); |
printk("killing %s.\n",current->comm); |
force_sig(SIGFPE,current); |
schedule(); |
} |
|
#endif /* CONFIG_MATH_EMULATION */ |
|
struct { |
unsigned short limit; |
unsigned long addr __attribute__((packed)); |
} idt_descriptor; |
|
void trap_init_f00f_bug(void) |
{ |
pgd_t * pgd; |
pmd_t * pmd; |
pte_t * pte; |
unsigned long page; |
unsigned long idtpage = (unsigned long)idt; |
struct desc_struct *alias_idt; |
|
printk("alias mapping IDT readonly ... "); |
|
/* just to get free address space */ |
page = (unsigned long) vmalloc (PAGE_SIZE); |
|
alias_idt = (void *)(page + (idtpage & ~PAGE_MASK)); |
idt_descriptor.limit = 256*8-1; |
idt_descriptor.addr = VMALLOC_VMADDR(alias_idt); |
|
/* |
* alias map the original idt to the alias page: |
*/ |
page = VMALLOC_VMADDR(page); |
pgd = pgd_offset(&init_mm, page); |
pmd = pmd_offset(pgd, page); |
pte = pte_offset(pmd, page); |
/* give memory back to the pool, don't need it */ |
free_page(pte_page(*pte)); |
/* ... and set the readonly alias */ |
set_pte(pte, mk_pte(idtpage & PAGE_MASK, PAGE_KERNEL)); |
*pte = pte_wrprotect(*pte); |
flush_tlb_all(); |
|
/* now we have the mapping ok, we can do LIDT */ |
__asm__ __volatile__("\tlidt %0": "=m" (idt_descriptor)); |
|
printk(" ... done\n"); |
} |
|
|
void trap_init(void) |
{ |
int i; |
struct desc_struct * p; |
static int smptrap=0; |
|
if(smptrap) |
{ |
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); |
load_ldt(0); |
return; |
} |
smptrap++; |
if (strncmp((char*)0x0FFFD9, "EISA", 4) == 0) |
EISA_bus = 1; |
set_call_gate(&default_ldt,lcall7); |
set_trap_gate(0,÷_error); |
set_trap_gate(1,&debug); |
set_trap_gate(2,&nmi); |
set_system_gate(3,&int3); /* int3-5 can be called from all */ |
set_system_gate(4,&overflow); |
set_system_gate(5,&bounds); |
set_trap_gate(6,&invalid_op); |
set_trap_gate(7,&device_not_available); |
set_trap_gate(8,&double_fault); |
set_trap_gate(9,&coprocessor_segment_overrun); |
set_trap_gate(10,&invalid_TSS); |
set_trap_gate(11,&segment_not_present); |
set_trap_gate(12,&stack_segment); |
set_trap_gate(13,&general_protection); |
set_trap_gate(14,&page_fault); |
set_trap_gate(15,&spurious_interrupt_bug); |
set_trap_gate(16,&coprocessor_error); |
set_trap_gate(17,&alignment_check); |
for (i=18;i<48;i++) |
set_trap_gate(i,&reserved); |
set_system_gate(0x80,&system_call); |
/* set up GDT task & ldt entries */ |
p = gdt+FIRST_TSS_ENTRY; |
set_tss_desc(p, &init_task.tss); |
p++; |
set_ldt_desc(p, &default_ldt, 1); |
p++; |
for(i=1 ; i<NR_TASKS ; i++) { |
p->a=p->b=0; |
p++; |
p->a=p->b=0; |
p++; |
} |
/* Clear NT, so that we won't have troubles with that later on */ |
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); |
load_TR(0); |
load_ldt(0); |
} |
/trampoline.S
0,0 → 1,74
! |
! Trampoline.S Derived from Setup.S by Linus Torvalds |
! |
! Entry: CS:IP point to the start of our code, we are |
! in real mode with no stack, but the rest of the |
! trampoline page to make our stack and everything else |
! is a mystery. |
! |
! In fact we don't actually need a stack so we don't |
! set one up. |
! |
! We jump into the boot/compressed/head.S code. So you'd |
! better be running a compressed kernel image or you |
! won't get very far. |
! |
#define __ASSEMBLY__ |
#include <asm/segment.h> |
|
.text |
extrn startup32 |
|
entry start |
start: |
! nop |
! jmp start ! Test |
mov ax,cs ! Code and data in the same place |
mov ds,ax ! |
mov cx,ax ! Pass stack info to the 32bit boot |
add cx,cx |
add cx,cx |
add cx,cx |
add cx,cx ! Segment -> Offset |
add cx, #4096 ! End of page is wanted |
mov bx,#1 ! Flag an SMP trampoline |
cli ! We should be safe anyway |
|
lidt idt_48 ! load idt with 0,0 |
lgdt gdt_48 ! load gdt with whatever is appropriate |
|
xor ax,ax |
inc ax ! protected mode (PE) bit |
lmsw ax ! Into protected mode |
jmp flush_instr |
flush_instr: |
jmpi 8192+startup32,KERNEL_CS ! Jump to the 32bit trampoline code |
! jmpi 0x100000,KERNEL_CS ! Jump into the 32bit startup |
! .byte 0x66,0x67 ! 32bit |
! .byte 0xea,0x00,0x00,0x10,0x00,0x10,0x00 !jmpi .0x100000,KERNEL_CS |
|
gdt: |
.word 0,0,0,0 ! dummy |
|
.word 0,0,0,0 ! unused |
|
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) |
.word 0x0000 ! base address=0 |
.word 0x9A00 ! code read/exec |
.word 0x00C0 ! granularity=4096, 386 |
|
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) |
.word 0x0000 ! base address=0 |
.word 0x9200 ! data read/write |
.word 0x00C0 ! granularity=4096, 386 |
|
idt_48: |
.word 0 ! idt limit=0 |
.word 0,0 ! idt base=0L |
|
gdt_48: |
.word 0x800 ! gdt limit=2048, 256 GDT entries |
.word 8192+gdt,0x0 ! gdt base = 8192+gdt (first SMP CPU) |
! we load the others with the first table |
! saves rewriting gdt_48 for each |
|
/irq.c
0,0 → 1,582
/* |
* linux/arch/i386/kernel/irq.c |
* |
* Copyright (C) 1992 Linus Torvalds |
* |
* This file contains the code used by various IRQ handling routines: |
* asking for different IRQ's should be done through these routines |
* instead of just grabbing them. Thus setups with different IRQ numbers |
* shouldn't result in any weird surprises, and installing new handlers |
* should be easier. |
*/ |
|
/* |
* IRQ's are in fact implemented a bit like signal handlers for the kernel. |
* Naturally it's not a 1:1 relation, but there are similarities. |
*/ |
|
#include <linux/ptrace.h> |
#include <linux/errno.h> |
#include <linux/kernel_stat.h> |
#include <linux/signal.h> |
#include <linux/sched.h> |
#include <linux/ioport.h> |
#include <linux/interrupt.h> |
#include <linux/timex.h> |
#include <linux/malloc.h> |
#include <linux/random.h> |
|
#include <asm/system.h> |
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/bitops.h> |
#include <asm/smp.h> |
|
#define CR0_NE 32 |
|
static unsigned char cache_21 = 0xff; |
static unsigned char cache_A1 = 0xff; |
|
#ifdef __SMP_PROF__ |
static unsigned int int_count[NR_CPUS][NR_IRQS] = {{0},}; |
#endif |
|
static inline void mask_irq(unsigned int irq_nr) |
{ |
unsigned char mask; |
|
mask = 1 << (irq_nr & 7); |
if (irq_nr < 8) { |
cache_21 |= mask; |
outb(cache_21,0x21); |
} else { |
cache_A1 |= mask; |
outb(cache_A1,0xA1); |
} |
} |
|
static inline void unmask_irq(unsigned int irq_nr) |
{ |
unsigned char mask; |
|
mask = ~(1 << (irq_nr & 7)); |
if (irq_nr < 8) { |
cache_21 &= mask; |
outb(cache_21,0x21); |
} else { |
cache_A1 &= mask; |
outb(cache_A1,0xA1); |
} |
} |
|
void disable_irq(unsigned int irq_nr) |
{ |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
mask_irq(irq_nr); |
restore_flags(flags); |
} |
|
void enable_irq(unsigned int irq_nr) |
{ |
unsigned long flags; |
save_flags(flags); |
cli(); |
unmask_irq(irq_nr); |
restore_flags(flags); |
} |
|
/* |
* This builds up the IRQ handler stubs using some ugly macros in irq.h |
* |
* These macros create the low-level assembly IRQ routines that do all |
* the operations that are needed to keep the AT interrupt-controller |
* happy. They are also written to be fast - and to disable interrupts |
* as little as humanly possible. |
* |
* NOTE! These macros expand to three different handlers for each line: one |
* complete handler that does all the fancy stuff (including signal handling), |
* and one fast handler that is meant for simple IRQ's that want to be |
* atomic. The specific handler is chosen depending on the SA_INTERRUPT |
* flag when installing a handler. Finally, one "bad interrupt" handler, that |
* is used when no handler is present. |
* |
* The timer interrupt is handled specially to insure that the jiffies |
* variable is updated at all times. Specifically, the timer interrupt is |
* just like the complete handlers except that it is invoked with interrupts |
* disabled and should never re-enable them. If other interrupts were |
* allowed to be processed while the timer interrupt is active, then the |
* other interrupts would have to avoid using the jiffies variable for delay |
* and interval timing operations to avoid hanging the system. |
*/ |
BUILD_TIMER_IRQ(FIRST,0,0x01) |
BUILD_IRQ(FIRST,1,0x02) |
BUILD_IRQ(FIRST,2,0x04) |
BUILD_IRQ(FIRST,3,0x08) |
BUILD_IRQ(FIRST,4,0x10) |
BUILD_IRQ(FIRST,5,0x20) |
BUILD_IRQ(FIRST,6,0x40) |
BUILD_IRQ(FIRST,7,0x80) |
BUILD_IRQ(SECOND,8,0x01) |
BUILD_IRQ(SECOND,9,0x02) |
BUILD_IRQ(SECOND,10,0x04) |
BUILD_IRQ(SECOND,11,0x08) |
BUILD_IRQ(SECOND,12,0x10) |
#ifdef __SMP__ |
BUILD_MSGIRQ(SECOND,13,0x20) |
#else |
BUILD_IRQ(SECOND,13,0x20) |
#endif |
BUILD_IRQ(SECOND,14,0x40) |
BUILD_IRQ(SECOND,15,0x80) |
#ifdef __SMP__ |
BUILD_RESCHEDIRQ(16) |
#endif |
|
/* |
* Pointers to the low-level handlers: first the general ones, then the |
* fast ones, then the bad ones. |
*/ |
static void (*interrupt[17])(void) = { |
IRQ0_interrupt, IRQ1_interrupt, IRQ2_interrupt, IRQ3_interrupt, |
IRQ4_interrupt, IRQ5_interrupt, IRQ6_interrupt, IRQ7_interrupt, |
IRQ8_interrupt, IRQ9_interrupt, IRQ10_interrupt, IRQ11_interrupt, |
IRQ12_interrupt, IRQ13_interrupt, IRQ14_interrupt, IRQ15_interrupt |
#ifdef __SMP__ |
,IRQ16_interrupt |
#endif |
}; |
|
static void (*fast_interrupt[16])(void) = { |
fast_IRQ0_interrupt, fast_IRQ1_interrupt, |
fast_IRQ2_interrupt, fast_IRQ3_interrupt, |
fast_IRQ4_interrupt, fast_IRQ5_interrupt, |
fast_IRQ6_interrupt, fast_IRQ7_interrupt, |
fast_IRQ8_interrupt, fast_IRQ9_interrupt, |
fast_IRQ10_interrupt, fast_IRQ11_interrupt, |
fast_IRQ12_interrupt, fast_IRQ13_interrupt, |
fast_IRQ14_interrupt, fast_IRQ15_interrupt |
}; |
|
static void (*bad_interrupt[16])(void) = { |
bad_IRQ0_interrupt, bad_IRQ1_interrupt, |
bad_IRQ2_interrupt, bad_IRQ3_interrupt, |
bad_IRQ4_interrupt, bad_IRQ5_interrupt, |
bad_IRQ6_interrupt, bad_IRQ7_interrupt, |
bad_IRQ8_interrupt, bad_IRQ9_interrupt, |
bad_IRQ10_interrupt, bad_IRQ11_interrupt, |
bad_IRQ12_interrupt, bad_IRQ13_interrupt, |
bad_IRQ14_interrupt, bad_IRQ15_interrupt |
}; |
|
/* |
* Initial irq handlers. |
*/ |
|
static void no_action(int cpl, void *dev_id, struct pt_regs *regs) { } |
|
#ifdef __SMP__ |
|
/* |
* On SMP boards, irq13 is used for interprocessor interrupts (IPI's). |
*/ |
static struct irqaction irq13 = { smp_message_irq, SA_INTERRUPT, 0, "IPI", NULL, NULL }; |
|
#else |
|
/* |
* Note that on a 486, we don't want to do a SIGFPE on a irq13 |
* as the irq is unreliable, and exception 16 works correctly |
* (ie as explained in the intel literature). On a 386, you |
* can't use exception 16 due to bad IBM design, so we have to |
* rely on the less exact irq13. |
* |
* Careful.. Not only is IRQ13 unreliable, but it is also |
* leads to races. IBM designers who came up with it should |
* be shot. |
*/ |
|
|
static void math_error_irq(int cpl, void *dev_id, struct pt_regs *regs) |
{ |
outb(0,0xF0); |
if (ignore_irq13 || !hard_math) |
return; |
math_error(); |
} |
|
static struct irqaction irq13 = { math_error_irq, 0, 0, "math error", NULL, NULL }; |
|
#endif |
|
/* |
* IRQ2 is cascade interrupt to second interrupt controller |
*/ |
static struct irqaction irq2 = { no_action, 0, 0, "cascade", NULL, NULL}; |
|
static struct irqaction *irq_action[16] = { |
NULL, NULL, NULL, NULL, |
NULL, NULL, NULL, NULL, |
NULL, NULL, NULL, NULL, |
NULL, NULL, NULL, NULL |
}; |
|
int get_irq_list(char *buf) |
{ |
int i, len = 0; |
struct irqaction * action; |
|
for (i = 0 ; i < 16 ; i++) { |
action = irq_action[i]; |
if (!action) |
continue; |
len += sprintf(buf+len, "%2d: %10u %c %s", |
i, kstat.interrupts[i], |
(action->flags & SA_INTERRUPT) ? '+' : ' ', |
action->name); |
for (action=action->next; action; action = action->next) { |
len += sprintf(buf+len, ",%s %s", |
(action->flags & SA_INTERRUPT) ? " +" : "", |
action->name); |
} |
len += sprintf(buf+len, "\n"); |
} |
/* |
* Linus - should you add NMI counts here ????? |
*/ |
#ifdef __SMP_PROF__ |
len+=sprintf(buf+len, "IPI: %8lu received\n", |
ipi_count); |
#endif |
return len; |
} |
|
#ifdef __SMP_PROF__ |
|
int get_smp_prof_list(char *buf) { |
int i,j, len = 0; |
struct irqaction * action; |
unsigned long sum_spins = 0; |
unsigned long sum_spins_syscall = 0; |
unsigned long sum_spins_sys_idle = 0; |
unsigned long sum_smp_idle_count = 0; |
|
for (i=0;i<smp_num_cpus;i++) { |
int cpunum = cpu_logical_map[i]; |
sum_spins+=smp_spins[cpunum]; |
sum_spins_syscall+=smp_spins_syscall[cpunum]; |
sum_spins_sys_idle+=smp_spins_sys_idle[cpunum]; |
sum_smp_idle_count+=smp_idle_count[cpunum]; |
} |
|
len += sprintf(buf+len,"CPUS: %10i \n", smp_num_cpus); |
len += sprintf(buf+len," SUM "); |
for (i=0;i<smp_num_cpus;i++) |
len += sprintf(buf+len," P%1d ",cpu_logical_map[i]); |
len += sprintf(buf+len,"\n"); |
for (i = 0 ; i < NR_IRQS ; i++) { |
action = *(i + irq_action); |
if (!action || !action->handler) |
continue; |
len += sprintf(buf+len, "%3d: %10d ", |
i, kstat.interrupts[i]); |
for (j=0;j<smp_num_cpus;j++) |
len+=sprintf(buf+len, "%10d ", |
int_count[cpu_logical_map[j]][i]); |
len += sprintf(buf+len, "%c %s", |
(action->flags & SA_INTERRUPT) ? '+' : ' ', |
action->name); |
for (action=action->next; action; action = action->next) { |
len += sprintf(buf+len, ",%s %s", |
(action->flags & SA_INTERRUPT) ? " +" : "", |
action->name); |
} |
len += sprintf(buf+len, "\n"); |
} |
len+=sprintf(buf+len, "LCK: %10lu", |
sum_spins); |
|
for (i=0;i<smp_num_cpus;i++) |
len+=sprintf(buf+len," %10lu",smp_spins[cpu_logical_map[i]]); |
|
len +=sprintf(buf+len," spins from int\n"); |
|
len+=sprintf(buf+len, "LCK: %10lu", |
sum_spins_syscall); |
|
for (i=0;i<smp_num_cpus;i++) |
len+=sprintf(buf+len," %10lu",smp_spins_syscall[cpu_logical_map[i]]); |
|
len +=sprintf(buf+len," spins from syscall\n"); |
|
len+=sprintf(buf+len, "LCK: %10lu", |
sum_spins_sys_idle); |
|
for (i=0;i<smp_num_cpus;i++) |
len+=sprintf(buf+len," %10lu",smp_spins_sys_idle[cpu_logical_map[i]]); |
|
len +=sprintf(buf+len," spins from sysidle\n"); |
len+=sprintf(buf+len,"IDLE %10lu",sum_smp_idle_count); |
|
for (i=0;i<smp_num_cpus;i++) |
len+=sprintf(buf+len," %10lu",smp_idle_count[cpu_logical_map[i]]); |
|
len +=sprintf(buf+len," idle ticks\n"); |
|
len+=sprintf(buf+len, "IPI: %10lu received\n", |
ipi_count); |
|
return len; |
} |
#endif |
|
|
|
/* |
* do_IRQ handles IRQ's that have been installed without the |
* SA_INTERRUPT flag: it uses the full signal-handling return |
* and runs with other interrupts enabled. All relatively slow |
* IRQ's should use this format: notably the keyboard/timer |
* routines. |
*/ |
asmlinkage void do_IRQ(int irq, struct pt_regs * regs) |
{ |
struct irqaction * action = *(irq + irq_action); |
int do_random = 0; |
int c,intm,mask; |
#ifdef IRQ_DEBUG |
static int count; |
if (smp_processor_id() != 0 && count++ < 1000) |
printk("IRQ %d: done by CPU %d\n",irq,smp_processor_id()); |
#endif |
if (irq >= 8) { |
c = cache_A1; |
intm = inb(0xA1); |
mask = 1 << (irq - 8); |
} else { |
c = cache_21; |
intm = inb(0x21); |
mask = 1 << irq; |
} |
if (!(c & mask) || !(intm & mask)) { |
#ifdef IRQ_DEBUG |
printk("IRQ %d (proc %d):cache_x1=0x%x,INT mask=0x%x\n", irq, smp_processor_id(),c,intm); |
#endif |
/* better to return because the interrupt may be asserted again, |
the bad thing is that we may loose some interrupts */ |
return; |
} |
#ifdef __SMP__ |
if(smp_threads_ready && active_kernel_processor!=smp_processor_id()) |
panic("IRQ %d: active processor set wrongly(%d not %d).\n", irq, active_kernel_processor, smp_processor_id()); |
#endif |
|
kstat.interrupts[irq]++; |
#ifdef __SMP_PROF__ |
int_count[smp_processor_id()][irq]++; |
#endif |
while (action) { |
do_random |= action->flags; |
action->handler(irq, action->dev_id, regs); |
action = action->next; |
} |
if (do_random & SA_SAMPLE_RANDOM) |
add_interrupt_randomness(irq); |
} |
|
/* |
* do_fast_IRQ handles IRQ's that don't need the fancy interrupt return |
* stuff - the handler is also running with interrupts disabled unless |
* it explicitly enables them later. |
*/ |
asmlinkage void do_fast_IRQ(int irq) |
{ |
struct irqaction * action = *(irq + irq_action); |
int do_random = 0; |
|
#ifdef __SMP__ |
/* IRQ 13 is allowed - that's a flush tlb */ |
if(smp_threads_ready && active_kernel_processor!=smp_processor_id() && irq!=13) |
panic("fast_IRQ %d: active processor set wrongly(%d not %d).\n", irq, active_kernel_processor, smp_processor_id()); |
#endif |
|
kstat.interrupts[irq]++; |
#ifdef __SMP_PROF__ |
int_count[smp_processor_id()][irq]++; |
#endif |
while (action) { |
do_random |= action->flags; |
action->handler(irq, action->dev_id, NULL); |
action = action->next; |
} |
if (do_random & SA_SAMPLE_RANDOM) |
add_interrupt_randomness(irq); |
} |
|
int setup_x86_irq(int irq, struct irqaction * new) |
{ |
int shared = 0; |
struct irqaction *old, **p; |
unsigned long flags; |
|
p = irq_action + irq; |
if ((old = *p) != NULL) { |
/* Can't share interrupts unless both agree to */ |
if (!(old->flags & new->flags & SA_SHIRQ)) |
return -EBUSY; |
|
/* Can't share interrupts unless both are same type */ |
if ((old->flags ^ new->flags) & SA_INTERRUPT) |
return -EBUSY; |
|
/* add new interrupt at end of irq queue */ |
do { |
p = &old->next; |
old = *p; |
} while (old); |
shared = 1; |
} |
|
if (new->flags & SA_SAMPLE_RANDOM) |
rand_initialize_irq(irq); |
|
save_flags(flags); |
cli(); |
*p = new; |
|
if (!shared) { |
if (new->flags & SA_INTERRUPT) |
set_intr_gate(0x20+irq,fast_interrupt[irq]); |
else |
set_intr_gate(0x20+irq,interrupt[irq]); |
unmask_irq(irq); |
} |
restore_flags(flags); |
return 0; |
} |
|
int request_irq(unsigned int irq, |
void (*handler)(int, void *, struct pt_regs *), |
unsigned long irqflags, |
const char * devname, |
void *dev_id) |
{ |
int retval; |
struct irqaction * action; |
|
if (irq > 15) |
return -EINVAL; |
if (!handler) |
return -EINVAL; |
|
action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL); |
if (!action) |
return -ENOMEM; |
|
action->handler = handler; |
action->flags = irqflags; |
action->mask = 0; |
action->name = devname; |
action->next = NULL; |
action->dev_id = dev_id; |
|
retval = setup_x86_irq(irq, action); |
|
if (retval) |
kfree(action); |
return retval; |
} |
|
void free_irq(unsigned int irq, void *dev_id) |
{ |
struct irqaction * action, **p; |
unsigned long flags; |
|
if (irq > 15) { |
printk("Trying to free IRQ%d\n",irq); |
return; |
} |
for (p = irq + irq_action; (action = *p) != NULL; p = &action->next) { |
if (action->dev_id != dev_id) |
continue; |
|
/* Found it - now free it */ |
save_flags(flags); |
cli(); |
*p = action->next; |
if (!irq[irq_action]) { |
mask_irq(irq); |
set_intr_gate(0x20+irq,bad_interrupt[irq]); |
} |
restore_flags(flags); |
kfree(action); |
return; |
} |
printk("Trying to free free IRQ%d\n",irq); |
} |
|
unsigned long probe_irq_on (void) |
{ |
unsigned int i, irqs = 0, irqmask; |
unsigned long delay; |
|
/* first, enable any unassigned irqs */ |
for (i = 15; i > 0; i--) { |
if (!irq_action[i]) { |
enable_irq(i); |
irqs |= (1 << i); |
} |
} |
|
/* wait for spurious interrupts to mask themselves out again */ |
for (delay = jiffies + HZ/10; delay > jiffies; ) |
/* about 100ms delay */; |
|
/* now filter out any obviously spurious interrupts */ |
irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21; |
return irqs & ~irqmask; |
} |
|
int probe_irq_off (unsigned long irqs) |
{ |
unsigned int i, irqmask; |
|
irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21; |
#ifdef DEBUG |
printk("probe_irq_off: irqs=0x%04lx irqmask=0x%04x\n", irqs, irqmask); |
#endif |
irqs &= irqmask; |
if (!irqs) |
return 0; |
i = ffz(~irqs); |
if (irqs != (irqs & (1 << i))) |
i = -i; |
return i; |
} |
|
void init_IRQ(void) |
{ |
int i; |
static unsigned char smptrap=0; |
if(smptrap) |
return; |
smptrap=1; |
|
/* set the clock to 100 Hz */ |
outb_p(0x34,0x43); /* binary, mode 2, LSB/MSB, ch 0 */ |
outb_p(LATCH & 0xff , 0x40); /* LSB */ |
outb(LATCH >> 8 , 0x40); /* MSB */ |
for (i = 0; i < 16 ; i++) |
set_intr_gate(0x20+i,bad_interrupt[i]); |
/* This bit is a hack because we don't send timer messages to all processors yet */ |
/* It has to be here .. it doesn't work if you put it down the bottom - assembler explodes 8) */ |
#ifdef __SMP__ |
set_intr_gate(0x20+i, interrupt[i]); /* IRQ '16' - IPI for rescheduling */ |
#endif |
request_region(0x20,0x20,"pic1"); |
request_region(0xa0,0x20,"pic2"); |
setup_x86_irq(2, &irq2); |
setup_x86_irq(13, &irq13); |
} |
/hexify.c
0,0 → 1,29
#include <stdio.h> |
|
|
int main() |
{ |
int c; |
int comma=0; |
int count=0; |
while((c=getchar())!=EOF) |
{ |
unsigned char x=c; |
if(comma) |
printf(","); |
else |
comma=1; |
if(count==8) |
{ |
count=0; |
printf("\n"); |
} |
if(count==0) |
printf("\t"); |
printf("0x%02X",c); |
count++; |
} |
if(count) |
printf("\n"); |
return 0; |
} |
/Makefile
0,0 → 1,76
# |
# Makefile for the linux kernel. |
# |
# Note! Dependencies are done automagically by 'make dep', which also |
# removes any old dependencies. DON'T put your own dependencies here |
# unless it's something special (ie not a .c file). |
# |
# Note 2! The CFLAGS definitions are now in the main makefile... |
|
#.S.s: |
# $(CPP) -D__ASSEMBLY__ -traditional $< -o $*.s |
|
ifdef SMP |
.S.o: |
$(CC) -D__ASSEMBLY__ $(AFLAGS) -traditional -c $< -o $*.o |
else |
.S.o: |
$(CC) -D__ASSEMBLY__ -traditional -c $< -o $*.o |
endif |
|
all: kernel.o head.o |
|
O_TARGET := kernel.o |
O_OBJS := process.o signal.o entry.o traps.o irq.o vm86.o bios32.o \ |
ptrace.o ioport.o ldt.o setup.o time.o sys_i386.o ksyms.o |
OX_OBJS := |
|
ifdef CONFIG_APM |
OX_OBJS += apm.o |
endif |
|
ifdef SMP |
|
ifdef CONFIG_MTRR |
O_OBJS += mtrr.o |
endif |
|
O_OBJS += smp.o |
|
head.o: head.S $(TOPDIR)/include/linux/tasks.h |
$(CC) -D__ASSEMBLY__ -D__SMP__ -traditional -c $*.S -o $*.o |
|
else |
|
head.o: head.S $(TOPDIR)/include/linux/tasks.h |
$(CC) -D__ASSEMBLY__ -traditional -c $*.S -o $*.o |
|
endif |
|
hexify: |
$(HOSTCC) hexify.c -o hexify |
|
smp.c: trampoline.hex |
|
trampoline.hex: trampoline hexify |
(dd if=trampoline bs=1 skip=32 | ./hexify >trampoline.hex ) |
|
trampoline: trampoline.o trampoline32.o |
$(LD86) -s -o $@ trampoline.o trampoline32.o |
|
trampoline.o: trampoline.s |
$(AS86) -o $@ $< |
|
trampoline32.o: trampoline32.s |
$(AS386) -o $@ $< |
|
trampoline.s: trampoline.S $(CONFIGURE) $(TOPDIR)/include/linux/config.h Makefile |
$(CPP) -D__SMP__ -traditional $< -o $@ |
|
trampoline32.s: trampoline32.S $(CONFIGURE) $(TOPDIR)/include/linux/config.h Makefile |
$(CPP) -D__SMP__ -traditional $< -o $@ |
|
clean: |
rm -f trampoline trampoline.hex hexify |
|
include $(TOPDIR)/Rules.make |