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