/**
* @file op_fixmap.c
* Horrible hacks for compatibility's sake.
* Based in part on arch/i386/kernel/mpparse.c
*
* @remark Copyright 2002 OProfile authors
* @remark Read the file COPYING
*
* @author John Levon
* @author Philippe Elie
*/
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/config.h>
#include <linux/pagemap.h>
#include <asm/io.h>
#include "oprofile.h"
#include "apic_compat.h"
#ifndef cpu_has_pge
#if V_BEFORE(2, 4, 0)
#define cpu_has_pge (test_bit(X86_FEATURE_PGE, &boot_cpu_data.x86_capability))
#else
#define cpu_has_pge (test_bit(X86_FEATURE_PGE, boot_cpu_data.x86_capability))
#endif
#endif
unsigned long virt_apic_base;
/* some static commented out to avoid warning, trying to figure out
* in exactly which circumstances we need this function is too prone
* error to be made w/o a full rebuild of supported kernel version */
/* how about __attribute__(__unused__) then ? */
/* FIXME is this comment right ? */
/* We don't take care about locking mm->page_table_lock because this is
* only needed on SMP and on SMP we have already a sensible setup */
/*static*/ void set_pte_phys(ulong vaddr, ulong phys)
{
pgprot_t prot;
pgd_t * pgd;
pmd_t * pmd;
pte_t * pte;
pgd = pgd_offset_k(vaddr);
pmd = pmd_offset(pgd, vaddr);
pte = pte_offset(pmd, vaddr);
prot = PAGE_KERNEL;
/* when !CONFIG_X86_LOCAL_APIC we can't rely on no cache flag set */
pgprot_val(prot) |= _PAGE_PCD;
if (cpu_has_pge)
pgprot_val(prot) |= _PAGE_GLOBAL;
set_pte(pte, mk_pte_phys(phys, prot));
__flush_tlb_one(vaddr);
}
/*static*/ void alloc_fixmap(void)
{
/* dirty hack :/ */
virt_apic_base = (ulong)vmalloc(4096);
set_pte_phys(virt_apic_base, APIC_DEFAULT_PHYS_BASE);
}
/*static*/ void free_fixmap(void)
{
ulong vaddr;
pgd_t * pgd;
pmd_t * pmd;
pte_t * pte;
vaddr = virt_apic_base;
if (!vaddr)
return;
pgd = pgd_offset_k(vaddr);
if (!pgd)
return;
pmd = pmd_offset(pgd, vaddr);
if (!pmd)
return;
pte = pte_offset(pmd, vaddr);
if (!pte)
return;
/* FIXME: is this the right way */
pte_clear(pte);
__flush_tlb_one(vaddr);
vfree((void*)virt_apic_base);
}
/*
* Make sure we can access the APIC. Some kernel versions create
* a meaningless zero-page mapping for the local APIC: we must
* detect this case and reset it.
*
* Some kernel versions/configs won't map the APIC at all, in
* which case we need to hack it ourselves.
*/
void fixmap_setup(void)
{
#if V_BEFORE(2, 4, 10)
#if defined(CONFIG_X86_LOCAL_APIC)
static int find_intel_smp(void);
if (!find_intel_smp()) {
set_pte_phys(__fix_to_virt(FIX_APIC_BASE),
APIC_DEFAULT_PHYS_BASE);
printk(KERN_INFO "oprofile: remapping local APIC.\n");
}
#else
alloc_fixmap();
printk(KERN_INFO "oprofile: mapping APIC.\n");
#endif /* CONFIG_X86_LOCAL_APIC */
#else
#if !defined(CONFIG_X86_LOCAL_APIC)
alloc_fixmap();
printk(KERN_INFO "oprofile: mapping APIC.\n");
#endif
#endif
}
void fixmap_restore(void)
{
#if V_BEFORE(2, 4, 10)
#if defined(CONFIG_X86_LOCAL_APIC)
/* Nothing to do */
#else
free_fixmap();
printk(KERN_INFO "oprofile: freeing APIC mapping.\n");
#endif /* CONFIG_X86_LOCAL_APIC */
#else
#if !defined(CONFIG_X86_LOCAL_APIC)
free_fixmap();
printk(KERN_INFO "oprofile: freeing APIC mapping.\n");
#endif
#endif
}
/* ---------------- MP table code ------------------ */
#if V_BEFORE(2, 4, 10) && defined(CONFIG_X86_LOCAL_APIC)
static int __init mpf_checksum(unsigned char * mp, int len)
{
int sum = 0;
while (len--)
sum += *mp++;
return sum & 0xFF;
}
static int __init mpf_table_ok(struct intel_mp_floating * mpf, unsigned long * bp)
{
if (*bp != SMP_MAGIC_IDENT)
return 0;
if (mpf->mpf_length != 1)
return 0;
if (mpf_checksum((unsigned char *)bp, 16))
return 0;
return (mpf->mpf_specification == 1 || mpf->mpf_specification == 4);
}
static int __init smp_scan_config (unsigned long base, unsigned long length)
{
unsigned long * bp = phys_to_virt(base);
struct intel_mp_floating * mpf;
while (length > 0) {
mpf = (struct intel_mp_floating *)bp;
if (mpf_table_ok(mpf, bp))
return 1;
bp += 4;
length -= 16;
}
return 0;
}
static int __init find_intel_smp(void)
{
unsigned int address;
if (smp_scan_config(0x0, 0x400) ||
smp_scan_config(639*0x400, 0x400) ||
smp_scan_config(0xF0000, 0x10000))
return 1;
address = *(unsigned short *)phys_to_virt(0x40E);
address <<= 4;
return smp_scan_config(address, 0x1000);
}
#endif /* V_BEFORE(2,4,10) && defined(CONFIG_X86_LOCAL_APIC) */