xref: /haiku/src/system/kernel/arch/x86/32/apm.cpp (revision 2b76973fa2401f7a5edf68e6470f3d3210cbcff3)
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 <safemode.h>
13 #include <boot/kernel_args.h>
14 
15 
16 #define TRACE_APM
17 #ifdef TRACE_APM
18 #	define TRACE(x) dprintf x
19 #else
20 #	define TRACE(x) ;
21 #endif
22 
23 #define APM_ERROR_DISABLED				0x01
24 #define APM_ERROR_DISCONNECTED			0x03
25 #define APM_ERROR_UNKNOWN_DEVICE		0x09
26 #define APM_ERROR_OUT_OF_RANGE			0x0a
27 #define APM_ERROR_DISENGAGED			0x0b
28 #define APM_ERROR_NOT_SUPPORTED			0x0c
29 #define APM_ERROR_RESUME_TIMER_DISABLED	0x0d
30 #define APM_ERROR_UNABLE_TO_ENTER_STATE	0x60
31 #define APM_ERROR_NO_EVENTS_PENDING		0x80
32 #define APM_ERROR_APM_NOT_PRESENT		0x86
33 
34 #define CARRY_FLAG	0x01
35 
36 extern segment_descriptor *gGDT;
37 extern void *gDmaAddress;
38 extern addr_t gBiosBase;
39 
40 static bool sAPMEnabled = false;
41 static struct {
42 	uint32	offset;
43 	uint16	segment;
44 } sAPMBiosEntry;
45 
46 
47 struct 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
59 static const char *
60 apm_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 
91 static status_t
92 call_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 
121 static status_t
122 apm_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 
136 static status_t
137 apm_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 
153 static status_t
154 apm_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 
165 static status_t
166 apm_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 
177 status_t
178 apm_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 
194 static void
195 apm_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 
206 static status_t
207 get_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 
235 static status_t
236 apm_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 			return user_memcpy(buffer, &info, sizeof(struct apm_battery_info));
250 	}
251 
252 	return B_BAD_VALUE;
253 }
254 
255 
256 //	#pragma mark -
257 
258 
259 status_t
260 apm_shutdown(void)
261 {
262 	if (!sAPMEnabled)
263 		return B_NOT_SUPPORTED;
264 
265 	cpu_status state = disable_interrupts();
266 
267 	status_t status = apm_set_state(APM_ALL_DEVICES, APM_POWER_STATE_OFF);
268 
269 	restore_interrupts(state);
270 	return status;
271 }
272 
273 
274 status_t
275 apm_init(kernel_args *args)
276 {
277 	const apm_info &info = args->platform_args.apm;
278 
279 	TRACE(("apm_init()\n"));
280 
281 	if ((info.version & 0xf) < 2) {
282 		// no APM or connect failed
283 		return B_ERROR;
284 	}
285 
286 	TRACE(("  code32: 0x%x, 0x%lx, length 0x%x\n",
287 		info.code32_segment_base, info.code32_segment_offset, info.code32_segment_length));
288 	TRACE(("  code16: 0x%x, length 0x%x\n",
289 		info.code16_segment_base, info.code16_segment_length));
290 	TRACE(("  data: 0x%x, length 0x%x\n",
291 		info.data_segment_base, info.data_segment_length));
292 
293 	// get APM setting - safemode settings override kernel settings
294 
295 	bool apm = false;
296 
297 	void *handle = load_driver_settings("kernel");
298 	if (handle != NULL) {
299 		apm = get_driver_boolean_parameter(handle, "apm", false, false);
300 		unload_driver_settings(handle);
301 	}
302 
303 	handle = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
304 	if (handle != NULL) {
305 		apm = !get_driver_boolean_parameter(handle, B_SAFEMODE_DISABLE_APM, !apm, !apm);
306 		unload_driver_settings(handle);
307 	}
308 
309 	if (!apm)
310 		return B_OK;
311 
312 	// Apparently, some broken BIOS try to access segment 0x40 for the BIOS
313 	// data section - we make sure it can by setting up the GDT accordingly
314 	// (the first 640kB are mapped as DMA area in arch_vm_init()).
315 	addr_t biosData = (addr_t)gDmaAddress + 0x400;
316 
317 	set_segment_descriptor(&gGDT[BIOS_DATA_SEGMENT >> 3],
318 		biosData, B_PAGE_SIZE - biosData,
319 		DT_DATA_WRITEABLE, DPL_KERNEL);
320 
321 	// TODO: test if APM segments really are in the BIOS ROM area (especially the
322 	//	data segment)
323 
324 	// Setup APM GDTs
325 
326 	// We ignore their length, and just set their segments to 64 kB which
327 	// shouldn't cause any headaches
328 
329 	set_segment_descriptor(&gGDT[APM_CODE32_SEGMENT >> 3],
330 		gBiosBase + (info.code32_segment_base << 4) - 0xe0000, 0xffff,
331 		DT_CODE_READABLE, DPL_KERNEL);
332 	set_segment_descriptor(&gGDT[APM_CODE16_SEGMENT >> 3],
333 		gBiosBase + (info.code16_segment_base << 4) - 0xe0000, 0xffff,
334 		DT_CODE_READABLE, DPL_KERNEL);
335 	gGDT[APM_CODE16_SEGMENT >> 3].d_b = 0;
336 		// 16-bit segment
337 
338 	if ((info.data_segment_base << 4) < 0xe0000) {
339 		// use the BIOS data segment as data segment for APM
340 
341 		if (info.data_segment_length == 0) {
342 			args->platform_args.apm.data_segment_length = B_PAGE_SIZE
343 				- info.data_segment_base;
344 		}
345 
346 		set_segment_descriptor(&gGDT[APM_DATA_SEGMENT >> 3],
347 			(addr_t)gDmaAddress + (info.data_segment_base << 4),
348 			info.data_segment_length,
349 			DT_DATA_WRITEABLE, DPL_KERNEL);
350 	} else {
351 		// use the BIOS area as data segment
352 		set_segment_descriptor(&gGDT[APM_DATA_SEGMENT >> 3],
353 			gBiosBase + (info.data_segment_base << 4) - 0xe0000, 0xffff,
354 			DT_DATA_WRITEABLE, DPL_KERNEL);
355 	}
356 
357 	// setup APM entry point
358 
359 	sAPMBiosEntry.segment = APM_CODE32_SEGMENT;
360 	sAPMBiosEntry.offset = info.code32_segment_offset;
361 
362 	apm_driver_version(info.version);
363 
364 	if (apm_enable_power_management(APM_ALL_DEVICES, true) != B_OK)
365 		dprintf("APM: cannot enable power management.\n");
366 	if (apm_engage_power_management(APM_ALL_DEVICES, true) != B_OK)
367 		dprintf("APM: cannot engage.\n");
368 
369 	register_kernel_daemon(apm_daemon, NULL, 10);
370 		// run the daemon once every second
371 
372 	register_generic_syscall(APM_SYSCALLS, apm_control, 1, 0);
373 	sAPMEnabled = true;
374 	return B_OK;
375 }
376 
377