1/*
2 * Copyright 2012, Alex Smith, alex@alex-smith.me.uk.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <drivers/bios.h>
8
9#include <KernelExport.h>
10
11#include <AutoDeleter.h>
12
13#include <arch/x86/arch_cpu.h>
14#include <vm/vm.h>
15#include <vm/VMAddressSpace.h>
16
17#include "x86emu.h"
18
19
20struct BIOSState {
21	BIOSState()
22		:
23		mapped_address(0),
24		area(-1),
25		allocated_size(0)
26	{
27	}
28
29	~BIOSState()
30	{
31		if (area >= 0)
32			delete_area(area);
33	}
34
35	addr_t		mapped_address;
36	area_id		area;
37	size_t		allocated_size;
38};
39
40
41// BIOS memory layout definitions.
42
43// Bottom of RAM contains the interrupt jump vectors
44// They need to be copied to our memory area
45static const uint32 kBDABase = 0;
46static const uint32 kBDASize = 0x1000;
47
48// Remaining part of RAM (left uninitialized initially)
49static const uint32 kRAMBase = 0x1000;
50static const uint32 kRAMSize = 0x8f000;
51
52// Upper part of address space: a bit of RAM, the video RAM, then the ROMs
53// Copied to the memory area as well, so the BIOS can be patched if needed.
54static const uint32 kEBDABase = 0x90000;
55static const uint32 kEBDASize = 0x70000;
56
57// Total size of the above
58static const uint32 kTotalSize = 0x100000;
59
60// The stack is dynamically allocated somewhere inside the RAM
61static const uint32 kStackSize = 0x1000;
62
63
64static sem_id sBIOSLock;
65static BIOSState* sCurrentBIOSState;
66
67
68//	#pragma mark - X86EMU hooks.
69
70
71static x86emuu8
72x86emu_pio_inb(X86EMU_pioAddr port)
73{
74	return in8(port);
75}
76
77
78static x86emuu16
79x86emu_pio_inw(X86EMU_pioAddr port)
80{
81	return in16(port);
82}
83
84
85static x86emuu32
86x86emu_pio_inl(X86EMU_pioAddr port)
87{
88	return in32(port);
89}
90
91
92static void
93x86emu_pio_outb(X86EMU_pioAddr port, x86emuu8 data)
94{
95	out8(data, port);
96}
97
98
99static void
100x86emu_pio_outw(X86EMU_pioAddr port, x86emuu16 data)
101{
102	out16(data, port);
103}
104
105
106static void
107x86emu_pio_outl(X86EMU_pioAddr port, x86emuu32 data)
108{
109	out32(data, port);
110}
111
112
113static X86EMU_pioFuncs x86emu_pio_funcs = {
114	x86emu_pio_inb,
115	x86emu_pio_inw,
116	x86emu_pio_inl,
117	x86emu_pio_outb,
118	x86emu_pio_outw,
119	x86emu_pio_outl,
120};
121
122
123static x86emuu8
124x86emu_mem_rdb(x86emuu32 addr)
125{
126	return *(x86emuu8*)((addr_t)addr + sCurrentBIOSState->mapped_address);
127}
128
129
130static x86emuu16
131x86emu_mem_rdw(x86emuu32 addr)
132{
133	return *(x86emuu16*)((addr_t)addr + sCurrentBIOSState->mapped_address);
134}
135
136
137static x86emuu32
138x86emu_mem_rdl(x86emuu32 addr)
139{
140	return *(x86emuu32*)((addr_t)addr + sCurrentBIOSState->mapped_address);
141}
142
143
144static void
145x86emu_mem_wrb(x86emuu32 addr, x86emuu8 val)
146{
147	*(x86emuu8*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
148}
149
150
151static void
152x86emu_mem_wrw(x86emuu32 addr, x86emuu16 val)
153{
154	*(x86emuu16*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
155}
156
157
158static void
159x86emu_mem_wrl(x86emuu32 addr, x86emuu32 val)
160{
161	*(x86emuu32*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
162}
163
164
165static X86EMU_memFuncs x86emu_mem_funcs = {
166	x86emu_mem_rdb,
167	x86emu_mem_rdw,
168	x86emu_mem_rdl,
169	x86emu_mem_wrb,
170	x86emu_mem_wrw,
171	x86emu_mem_wrl,
172};
173
174
175//	#pragma mark -
176
177
178static void*
179bios_allocate_mem(bios_state* state, size_t size)
180{
181	// Simple allocator for memory to pass to the BIOS. No need for a complex
182	// allocator here, there is only a few allocations per BIOS usage.
183
184	size = ROUNDUP(size, 4);
185
186	if (state->allocated_size + size > kRAMSize)
187		return NULL;
188
189	void* address
190		= (void*)(state->mapped_address + kRAMBase + state->allocated_size);
191	state->allocated_size += size;
192
193	return address;
194}
195
196
197static uint32
198bios_physical_address(bios_state* state, void* virtualAddress)
199{
200	return (uint32)((addr_t)virtualAddress - state->mapped_address);
201}
202
203
204static void*
205bios_virtual_address(bios_state* state, uint32 physicalAddress)
206{
207	return (void*)((addr_t)physicalAddress + state->mapped_address);
208}
209
210
211static status_t
212bios_prepare(bios_state** _state)
213{
214	status_t status;
215
216	BIOSState* state = new(std::nothrow) BIOSState;
217	if (state == NULL)
218		return B_NO_MEMORY;
219	ObjectDeleter<BIOSState> stateDeleter(state);
220
221	// Reserve a chunk of address space to map at.
222	status = vm_reserve_address_range(VMAddressSpace::KernelID(),
223		(void**)&state->mapped_address, B_ANY_KERNEL_ADDRESS,
224		kTotalSize, 0);
225	if (status != B_OK)
226		return status;
227
228	// Map RAM for for the BIOS.
229	state->area = create_area("bios", (void**)&state->mapped_address,
230		B_EXACT_ADDRESS, kTotalSize, B_NO_LOCK, B_KERNEL_READ_AREA
231			| B_KERNEL_WRITE_AREA);
232	if (state->area < B_OK) {
233		vm_unreserve_address_range(VMAddressSpace::KernelID(),
234			(void*)state->mapped_address, kTotalSize);
235		return state->area;
236	}
237
238	// Copy the interrupt vectors and the BIOS data area.
239	status = vm_memcpy_from_physical((void*)state->mapped_address, kBDABase,
240		kBDASize, false);
241	if (status != B_OK) {
242		vm_unreserve_address_range(VMAddressSpace::KernelID(),
243			(void*)state->mapped_address, kTotalSize);
244		return status;
245	}
246
247	// Map the extended BIOS data area and VGA memory.
248	void* address = (void*)(state->mapped_address + kEBDABase);
249	status = vm_memcpy_from_physical(address, kEBDABase, kEBDASize, false);
250
251	if (status != B_OK) {
252		vm_unreserve_address_range(VMAddressSpace::KernelID(),
253			(void*)state->mapped_address, kTotalSize);
254		return status;
255	}
256
257	// Attempt to acquire exclusive access to the BIOS.
258	acquire_sem(sBIOSLock);
259
260	stateDeleter.Detach();
261	*_state = state;
262	return B_OK;
263}
264
265
266static status_t
267bios_interrupt(bios_state* state, uint8 vector, bios_regs* regs)
268{
269	sCurrentBIOSState = state;
270
271	// Allocate a stack.
272	void* stack = bios_allocate_mem(state, kStackSize);
273	if (stack == NULL)
274		return B_NO_MEMORY;
275	uint32 stackTop = bios_physical_address(state, stack) + kStackSize;
276
277	// X86EMU finishes when it encounters a HLT instruction, allocate a
278	// byte to store one in and set the return address of the interrupt to
279	// point to it.
280	void* halt = bios_allocate_mem(state, 1);
281	if (halt == NULL)
282		return B_NO_MEMORY;
283	*(uint8*)halt = 0xF4;
284
285	// Copy in the registers.
286	memset(&M, 0, sizeof(M));
287	M.x86.R_EAX = regs->eax;
288	M.x86.R_EBX = regs->ebx;
289	M.x86.R_ECX = regs->ecx;
290	M.x86.R_EDX = regs->edx;
291	M.x86.R_EDI = regs->edi;
292	M.x86.R_ESI = regs->esi;
293	M.x86.R_EBP = regs->ebp;
294	M.x86.R_EFLG = regs->eflags | X86_EFLAGS_INTERRUPT | X86_EFLAGS_RESERVED1;
295	M.x86.R_EIP = bios_physical_address(state, halt);
296	M.x86.R_CS = 0x0;
297	M.x86.R_DS = regs->ds;
298	M.x86.R_ES = regs->es;
299	M.x86.R_FS = regs->fs;
300	M.x86.R_GS = regs->gs;
301	M.x86.R_SS = stackTop >> 4;
302	M.x86.R_ESP = stackTop - (M.x86.R_SS << 4);
303
304	/* Run the interrupt. */
305	X86EMU_setupPioFuncs(&x86emu_pio_funcs);
306	X86EMU_setupMemFuncs(&x86emu_mem_funcs);
307	X86EMU_prepareForInt(vector);
308	X86EMU_exec();
309
310	// Copy back modified data.
311	regs->eax = M.x86.R_EAX;
312	regs->ebx = M.x86.R_EBX;
313	regs->ecx = M.x86.R_ECX;
314	regs->edx = M.x86.R_EDX;
315	regs->edi = M.x86.R_EDI;
316	regs->esi = M.x86.R_ESI;
317	regs->ebp = M.x86.R_EBP;
318	regs->eflags = M.x86.R_EFLG;
319	regs->ds = M.x86.R_DS;
320	regs->es = M.x86.R_ES;
321	regs->fs = M.x86.R_FS;
322	regs->gs = M.x86.R_GS;
323
324	return B_OK;
325}
326
327
328static void
329bios_finish(bios_state* state)
330{
331	release_sem(sBIOSLock);
332
333	delete state;
334}
335
336
337static status_t
338std_ops(int32 op, ...)
339{
340	switch (op) {
341		case B_MODULE_INIT:
342			sBIOSLock = create_sem(1, "bios lock");
343			if (sBIOSLock < B_OK)
344				return sBIOSLock;
345
346			return B_OK;
347		case B_MODULE_UNINIT:
348			delete_sem(sBIOSLock);
349			return B_OK;
350		default:
351			return B_ERROR;
352	}
353}
354
355
356static bios_module_info sBIOSModule = {
357	{
358		B_BIOS_MODULE_NAME,
359		0,
360		std_ops
361	},
362
363	bios_prepare,
364	bios_interrupt,
365	bios_finish,
366	bios_allocate_mem,
367	bios_physical_address,
368	bios_virtual_address
369};
370
371
372module_info *modules[] = {
373	(module_info*)&sBIOSModule,
374	NULL
375};
376