1/**
2 * @file op_fixmap.c
3 * Horrible hacks for compatibility's sake.
4 * Based in part on arch/i386/kernel/mpparse.c
5 *
6 * @remark Copyright 2002 OProfile authors
7 * @remark Read the file COPYING
8 *
9 * @author John Levon
10 * @author Philippe Elie
11 */
12
13#include <linux/mm.h>
14#include <linux/init.h>
15#include <linux/config.h>
16#include <linux/pagemap.h>
17#include <asm/io.h>
18
19#include "oprofile.h"
20#include "apic_compat.h"
21
22#ifndef cpu_has_pge
23#if V_BEFORE(2, 4, 0)
24#define cpu_has_pge (test_bit(X86_FEATURE_PGE, &boot_cpu_data.x86_capability))
25#else
26#define cpu_has_pge (test_bit(X86_FEATURE_PGE, boot_cpu_data.x86_capability))
27#endif
28#endif
29
30unsigned long virt_apic_base;
31
32/* some static commented out to avoid warning, trying to figure out
33 * in exactly which circumstances we need this function is too prone
34 * error to be made w/o a full rebuild of supported kernel version */
35/* how about __attribute__(__unused__) then ? */
36
37/* FIXME is this comment right ? */
38/* We don't take care about locking mm->page_table_lock because this is
39 * only needed on SMP and on SMP we have already a sensible setup */
40
41/*static*/ void set_pte_phys(ulong vaddr, ulong phys)
42{
43	pgprot_t prot;
44	pgd_t * pgd;
45	pmd_t * pmd;
46	pte_t * pte;
47
48	pgd = pgd_offset_k(vaddr);
49	pmd = pmd_offset(pgd, vaddr);
50	pte = pte_offset(pmd, vaddr);
51	prot = PAGE_KERNEL;
52	/* when !CONFIG_X86_LOCAL_APIC we can't rely on no cache flag set */
53	pgprot_val(prot) |= _PAGE_PCD;
54	if (cpu_has_pge)
55		pgprot_val(prot) |= _PAGE_GLOBAL;
56	set_pte(pte, mk_pte_phys(phys, prot));
57	__flush_tlb_one(vaddr);
58}
59
60/*static*/ void alloc_fixmap(void)
61{
62	/* dirty hack :/ */
63	virt_apic_base = (ulong)vmalloc(4096);
64	set_pte_phys(virt_apic_base, APIC_DEFAULT_PHYS_BASE);
65}
66
67/*static*/ void free_fixmap(void)
68{
69	ulong vaddr;
70	pgd_t * pgd;
71	pmd_t * pmd;
72	pte_t * pte;
73
74	vaddr = virt_apic_base;
75	if (!vaddr)
76		return;
77
78	pgd = pgd_offset_k(vaddr);
79	if (!pgd)
80		return;
81
82	pmd = pmd_offset(pgd, vaddr);
83	if (!pmd)
84		return;
85
86	pte = pte_offset(pmd, vaddr);
87	if (!pte)
88		return;
89
90	/* FIXME: is this the right way */
91	pte_clear(pte);
92	__flush_tlb_one(vaddr);
93
94	vfree((void*)virt_apic_base);
95}
96
97/*
98 * Make sure we can access the APIC. Some kernel versions create
99 * a meaningless zero-page mapping for the local APIC: we must
100 * detect this case and reset it.
101 *
102 * Some kernel versions/configs won't map the APIC at all, in
103 * which case we need to hack it ourselves.
104 */
105void fixmap_setup(void)
106{
107#if V_BEFORE(2, 4, 10)
108#if defined(CONFIG_X86_LOCAL_APIC)
109	static int find_intel_smp(void);
110
111	if (!find_intel_smp()) {
112		set_pte_phys(__fix_to_virt(FIX_APIC_BASE),
113			APIC_DEFAULT_PHYS_BASE);
114		printk(KERN_INFO "oprofile: remapping local APIC.\n");
115	}
116#else
117	alloc_fixmap();
118	printk(KERN_INFO "oprofile: mapping APIC.\n");
119#endif /* CONFIG_X86_LOCAL_APIC */
120#else
121#if !defined(CONFIG_X86_LOCAL_APIC)
122	alloc_fixmap();
123	printk(KERN_INFO "oprofile: mapping APIC.\n");
124#endif
125#endif
126}
127
128void fixmap_restore(void)
129{
130#if V_BEFORE(2, 4, 10)
131#if defined(CONFIG_X86_LOCAL_APIC)
132	/* Nothing to do */
133#else
134	free_fixmap();
135	printk(KERN_INFO "oprofile: freeing APIC mapping.\n");
136#endif /* CONFIG_X86_LOCAL_APIC */
137#else
138#if !defined(CONFIG_X86_LOCAL_APIC)
139	free_fixmap();
140	printk(KERN_INFO "oprofile: freeing APIC mapping.\n");
141#endif
142#endif
143}
144
145/* ---------------- MP table code ------------------ */
146
147#if V_BEFORE(2, 4, 10) && defined(CONFIG_X86_LOCAL_APIC)
148
149static int __init mpf_checksum(unsigned char * mp, int len)
150{
151	int sum = 0;
152
153	while (len--)
154		sum += *mp++;
155
156	return sum & 0xFF;
157}
158
159static int __init mpf_table_ok(struct intel_mp_floating * mpf, unsigned long * bp)
160{
161	if (*bp != SMP_MAGIC_IDENT)
162		return 0;
163	if (mpf->mpf_length != 1)
164		return 0;
165	if (mpf_checksum((unsigned char *)bp, 16))
166		return 0;
167
168	return (mpf->mpf_specification == 1 || mpf->mpf_specification == 4);
169}
170
171static int __init smp_scan_config (unsigned long base, unsigned long length)
172{
173	unsigned long * bp = phys_to_virt(base);
174	struct intel_mp_floating * mpf;
175
176	while (length > 0) {
177		mpf = (struct intel_mp_floating *)bp;
178		if (mpf_table_ok(mpf, bp))
179			return 1;
180		bp += 4;
181		length -= 16;
182	}
183	return 0;
184}
185
186static int __init find_intel_smp(void)
187{
188	unsigned int address;
189
190	if (smp_scan_config(0x0, 0x400) ||
191		smp_scan_config(639*0x400, 0x400) ||
192		smp_scan_config(0xF0000, 0x10000))
193		return 1;
194
195	address = *(unsigned short *)phys_to_virt(0x40E);
196	address <<= 4;
197	return smp_scan_config(address, 0x1000);
198}
199
200#endif /* V_BEFORE(2,4,10) && defined(CONFIG_X86_LOCAL_APIC) */
201