1/*
2 * Copyright 2006-2008, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <KernelExport.h>
8
9#include <apm.h>
10#include <descriptors.h>
11#include <generic_syscall.h>
12#include <kernel.h>
13#include <safemode.h>
14#include <boot/kernel_args.h>
15
16
17#define TRACE_APM
18#ifdef TRACE_APM
19#	define TRACE(x) dprintf x
20#else
21#	define TRACE(x) ;
22#endif
23
24#define APM_ERROR_DISABLED				0x01
25#define APM_ERROR_DISCONNECTED			0x03
26#define APM_ERROR_UNKNOWN_DEVICE		0x09
27#define APM_ERROR_OUT_OF_RANGE			0x0a
28#define APM_ERROR_DISENGAGED			0x0b
29#define APM_ERROR_NOT_SUPPORTED			0x0c
30#define APM_ERROR_RESUME_TIMER_DISABLED	0x0d
31#define APM_ERROR_UNABLE_TO_ENTER_STATE	0x60
32#define APM_ERROR_NO_EVENTS_PENDING		0x80
33#define APM_ERROR_APM_NOT_PRESENT		0x86
34
35#define CARRY_FLAG	0x01
36
37extern void *gDmaAddress;
38extern addr_t gBiosBase;
39
40static bool sAPMEnabled = false;
41static struct {
42	uint32	offset;
43	uint16	segment;
44} sAPMBiosEntry;
45
46
47struct bios_regs {
48	bios_regs() : eax(0), ebx(0), ecx(0), edx(0), esi(0), flags(0) {}
49	uint32	eax;
50	uint32	ebx;
51	uint32	ecx;
52	uint32	edx;
53	uint32	esi;
54	uint32	flags;
55};
56
57
58#ifdef TRACE_APM
59static const char *
60apm_error(uint32 error)
61{
62	switch (error >> 8) {
63		case APM_ERROR_DISABLED:
64			return "Power Management disabled";
65		case APM_ERROR_DISCONNECTED:
66			return "Interface disconnected";
67		case APM_ERROR_UNKNOWN_DEVICE:
68			return "Unrecognized device ID";
69		case APM_ERROR_OUT_OF_RANGE:
70			return "Parameter value out of range";
71		case APM_ERROR_DISENGAGED:
72			return "Interface not engaged";
73		case APM_ERROR_NOT_SUPPORTED:
74			return "Function not supported";
75		case APM_ERROR_RESUME_TIMER_DISABLED:
76			return "Resume timer disabled";
77		case APM_ERROR_UNABLE_TO_ENTER_STATE:
78			return "Unable to enter requested state";
79		case APM_ERROR_NO_EVENTS_PENDING:
80			return "No power management events pending";
81		case APM_ERROR_APM_NOT_PRESENT:
82			return "APM not present";
83
84		default:
85			return "Unknown error";
86	}
87}
88#endif	// TRACE_APM
89
90
91static status_t
92call_apm_bios(bios_regs *regs)
93{
94#if __GNUC__ < 4
95	// TODO: Fix this for GCC 4.3! The direct reference to sAPMBiosEntry
96	// in the asm below causes undefined references.
97	asm volatile(
98		"pushfl; "
99		"pushl %%ebp; "
100		"lcall *%%cs:sAPMBiosEntry; "
101		"popl %%ebp; "
102		"pushfl; "
103		"popl %%edi; "
104		"movl %%edi, %5; "
105		"popfl; "
106		: "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx), "=d" (regs->edx),
107		  "=S" (regs->esi), "=m" (regs->flags)
108		: "a" (regs->eax), "b" (regs->ebx), "c" (regs->ecx)
109		: "memory", "edi", "cc");
110
111	if (regs->flags & CARRY_FLAG)
112		return B_ERROR;
113
114	return B_OK;
115#else
116	return B_ERROR;
117#endif
118}
119
120
121static status_t
122apm_get_event(uint16 &event, uint16 &info)
123{
124	bios_regs regs;
125	regs.eax = BIOS_APM_GET_EVENT;
126
127	if (call_apm_bios(&regs) != B_OK)
128		return B_ERROR;
129
130	event = regs.ebx & 0xffff;
131	info = regs.ecx & 0xffff;
132	return B_OK;
133}
134
135
136static status_t
137apm_set_state(uint16 device, uint16 state)
138{
139	bios_regs regs;
140	regs.eax = BIOS_APM_SET_STATE;
141	regs.ebx = device;
142	regs.ecx = state;
143
144	status_t status = call_apm_bios(&regs);
145	if (status == B_OK)
146		return B_OK;
147
148	TRACE(("apm_set_state() error: %s\n", apm_error(regs.eax)));
149	return status;
150}
151
152
153static status_t
154apm_enable_power_management(uint16 device, bool enable)
155{
156	bios_regs regs;
157	regs.eax = BIOS_APM_ENABLE;
158	regs.ebx = device;
159	regs.ecx = enable ? 0x01 : 0x00;
160
161	return call_apm_bios(&regs);
162}
163
164
165static status_t
166apm_engage_power_management(uint16 device, bool engage)
167{
168	bios_regs regs;
169	regs.eax = BIOS_APM_ENGAGE;
170	regs.ebx = device;
171	regs.ecx = engage ? 0x01 : 0x00;
172
173	return call_apm_bios(&regs);
174}
175
176
177status_t
178apm_driver_version(uint16 version)
179{
180	dprintf("version: %x\n", version);
181	bios_regs regs;
182	regs.eax = BIOS_APM_VERSION;
183	regs.ecx = version;
184
185	if (call_apm_bios(&regs) != B_OK)
186		return B_ERROR;
187
188	dprintf("eax: %lx, flags: %lx\n", regs.eax, regs.flags);
189
190	return B_OK;
191}
192
193
194static void
195apm_daemon(void *arg, int iteration)
196{
197	uint16 event;
198	uint16 info;
199	if (apm_get_event(event, info) != B_OK)
200		return;
201
202	dprintf("APM event: %x, info: %x\n", event, info);
203}
204
205
206static status_t
207get_apm_battery_info(apm_battery_info *info)
208{
209	bios_regs regs;
210	regs.eax = BIOS_APM_GET_POWER_STATUS;
211	regs.ebx = APM_ALL_DEVICES;
212	regs.ecx = 0;
213
214	status_t status = call_apm_bios(&regs);
215	if (status != B_OK)
216		return status;
217
218	uint16 lineStatus = (regs.ebx >> 8) & 0xff;
219	if (lineStatus == 0xff)
220		return B_NOT_SUPPORTED;
221
222	info->online = lineStatus != 0 && lineStatus != 2;
223	info->percent = regs.ecx & 0xff;
224	if (info->percent > 100 || info->percent < 0)
225		info->percent = -1;
226
227	info->time_left = info->percent >= 0 ? (int32)(regs.edx & 0xffff) : -1;
228	if (info->time_left & 0x8000)
229		info->time_left = (info->time_left & 0x7fff) * 60;
230
231	return B_OK;
232}
233
234
235static status_t
236apm_control(const char *subsystem, uint32 function,
237	void *buffer, size_t bufferSize)
238{
239	struct apm_battery_info info;
240	if (bufferSize != sizeof(struct apm_battery_info))
241		return B_BAD_VALUE;
242
243	switch (function) {
244		case APM_GET_BATTERY_INFO:
245			status_t status = get_apm_battery_info(&info);
246			if (status < B_OK)
247				return status;
248
249			if (buffer == NULL || !IS_USER_ADDRESS(buffer))
250				return B_BAD_ADDRESS;
251			return user_memcpy(buffer, &info, sizeof(struct apm_battery_info));
252	}
253
254	return B_BAD_VALUE;
255}
256
257
258//	#pragma mark -
259
260
261status_t
262apm_shutdown(void)
263{
264	if (!sAPMEnabled)
265		return B_NOT_SUPPORTED;
266
267	cpu_status state = disable_interrupts();
268
269	status_t status = apm_set_state(APM_ALL_DEVICES, APM_POWER_STATE_OFF);
270
271	restore_interrupts(state);
272	return status;
273}
274
275
276status_t
277apm_init(kernel_args *args)
278{
279	const apm_info &info = args->platform_args.apm;
280
281	TRACE(("apm_init()\n"));
282
283	if ((info.version & 0xf) < 2) {
284		// no APM or connect failed
285		return B_ERROR;
286	}
287
288	TRACE(("  code32: 0x%x, 0x%lx, length 0x%x\n",
289		info.code32_segment_base, info.code32_segment_offset, info.code32_segment_length));
290	TRACE(("  code16: 0x%x, length 0x%x\n",
291		info.code16_segment_base, info.code16_segment_length));
292	TRACE(("  data: 0x%x, length 0x%x\n",
293		info.data_segment_base, info.data_segment_length));
294
295	// get APM setting - safemode settings override kernel settings
296
297	bool apm = false;
298
299	void *handle = load_driver_settings("kernel");
300	if (handle != NULL) {
301		apm = get_driver_boolean_parameter(handle, "apm", false, false);
302		unload_driver_settings(handle);
303	}
304
305	handle = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
306	if (handle != NULL) {
307		apm = !get_driver_boolean_parameter(handle, B_SAFEMODE_DISABLE_APM, !apm, !apm);
308		unload_driver_settings(handle);
309	}
310
311	if (!apm)
312		return B_OK;
313
314	// Apparently, some broken BIOS try to access segment 0x40 for the BIOS
315	// data section - we make sure it can by setting up the GDT accordingly
316	// (the first 640kB are mapped as DMA area in arch_vm_init()).
317	addr_t biosData = (addr_t)gDmaAddress + 0x400;
318
319	for (uint32 i = 0; i < args->num_cpus; i++) {
320		segment_descriptor* gdt = get_gdt(i);
321
322		set_segment_descriptor(&gdt[BIOS_DATA_SEGMENT], biosData,
323			B_PAGE_SIZE - biosData, DT_DATA_WRITEABLE, DPL_KERNEL);
324
325		// TODO: test if APM segments really are in the BIOS ROM area
326		// (especially the data segment)
327
328		// Setup APM GDTs
329
330		// We ignore their length, and just set their segments to 64 kB which
331		// shouldn't cause any headaches
332
333		set_segment_descriptor(&gdt[APM_CODE32_SEGMENT],
334			gBiosBase + (info.code32_segment_base << 4) - 0xe0000, 0xffff,
335			DT_CODE_READABLE, DPL_KERNEL);
336		set_segment_descriptor(&gdt[APM_CODE16_SEGMENT],
337			gBiosBase + (info.code16_segment_base << 4) - 0xe0000, 0xffff,
338			DT_CODE_READABLE, DPL_KERNEL);
339		gdt[APM_CODE16_SEGMENT].d_b = 0;
340			// 16-bit segment
341
342		if ((info.data_segment_base << 4) < 0xe0000) {
343			// use the BIOS data segment as data segment for APM
344
345			if (info.data_segment_length == 0) {
346				args->platform_args.apm.data_segment_length = B_PAGE_SIZE
347					- info.data_segment_base;
348			}
349
350			set_segment_descriptor(&gdt[APM_DATA_SEGMENT],
351				(addr_t)gDmaAddress + (info.data_segment_base << 4),
352				info.data_segment_length,
353				DT_DATA_WRITEABLE, DPL_KERNEL);
354		} else {
355			// use the BIOS area as data segment
356			set_segment_descriptor(&gdt[APM_DATA_SEGMENT],
357				gBiosBase + (info.data_segment_base << 4) - 0xe0000, 0xffff,
358				DT_DATA_WRITEABLE, DPL_KERNEL);
359		}
360	}
361
362	// setup APM entry point
363
364	sAPMBiosEntry.segment = (APM_CODE32_SEGMENT << 3) | DPL_KERNEL;
365	sAPMBiosEntry.offset = info.code32_segment_offset;
366
367	apm_driver_version(info.version);
368
369	if (apm_enable_power_management(APM_ALL_DEVICES, true) != B_OK)
370		dprintf("APM: cannot enable power management.\n");
371	if (apm_engage_power_management(APM_ALL_DEVICES, true) != B_OK)
372		dprintf("APM: cannot engage.\n");
373
374	register_kernel_daemon(apm_daemon, NULL, 10);
375		// run the daemon once every second
376
377	register_generic_syscall(APM_SYSCALLS, apm_control, 1, 0);
378	sAPMEnabled = true;
379	return B_OK;
380}
381
382