-
Notifications
You must be signed in to change notification settings - Fork 641
/
acpipwr.c
170 lines (152 loc) · 4.83 KB
/
acpipwr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
* Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <unistd.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/io.h>
#include <ipxe/acpi.h>
#include <ipxe/acpipwr.h>
/** @file
*
* ACPI power off
*
*/
/** Colour for debug messages */
#define colour FADT_SIGNATURE
/** _S5_ signature */
#define S5_SIGNATURE ACPI_SIGNATURE ( '_', 'S', '5', '_' )
/**
* Extract \_Sx value from DSDT/SSDT
*
* @v zsdt DSDT or SSDT
* @v len Length of DSDT/SSDT
* @v offset Offset of signature within DSDT/SSDT
* @v data Data buffer
* @ret rc Return status code
*
* In theory, extracting the \_Sx value from the DSDT/SSDT requires a
* full ACPI parser plus some heuristics to work around the various
* broken encodings encountered in real ACPI implementations.
*
* In practice, we can get the same result by scanning through the
* DSDT/SSDT for the signature (e.g. "_S5_"), extracting the first
* four bytes, removing any bytes with bit 3 set, and treating
* whatever is left as a little-endian value. This is one of the
* uglier hacks I have ever implemented, but it's still prettier than
* the ACPI specification itself.
*/
static int acpi_extract_sx ( userptr_t zsdt, size_t len, size_t offset,
void *data ) {
unsigned int *sx = data;
uint8_t bytes[4];
uint8_t *byte;
/* Skip signature and package header */
offset += ( 4 /* signature */ + 3 /* package header */ );
/* Sanity check */
if ( ( offset + sizeof ( bytes ) /* value */ ) > len ) {
return -EINVAL;
}
/* Read first four bytes of value */
copy_from_user ( bytes, zsdt, offset, sizeof ( bytes ) );
DBGC ( colour, "ACPI found \\_Sx containing %02x:%02x:%02x:%02x\n",
bytes[0], bytes[1], bytes[2], bytes[3] );
/* Extract \Sx value. There are three potential encodings
* that we might encounter:
*
* - SLP_TYPa, SLP_TYPb, rsvd, rsvd
*
* - <byteprefix>, SLP_TYPa, <byteprefix>, SLP_TYPb, ...
*
* - <dwordprefix>, SLP_TYPa, SLP_TYPb, 0, 0
*
* Since <byteprefix> and <dwordprefix> both have bit 3 set,
* and valid SLP_TYPx must have bit 3 clear (since SLP_TYPx is
* a 3-bit field), we can just skip any bytes with bit 3 set.
*/
byte = bytes;
if ( *byte & 0x08 )
byte++;
*sx = *(byte++);
if ( *byte & 0x08 )
byte++;
*sx |= ( *byte << 8 );
return 0;
}
/**
* Power off the computer using ACPI
*
* @ret rc Return status code
*/
int acpi_poweroff ( void ) {
struct acpi_fadt fadtab;
userptr_t fadt;
unsigned int pm1a_cnt_blk;
unsigned int pm1b_cnt_blk;
unsigned int pm1a_cnt;
unsigned int pm1b_cnt;
unsigned int slp_typa;
unsigned int slp_typb;
unsigned int s5;
int rc;
/* Locate FADT */
fadt = acpi_table ( FADT_SIGNATURE, 0 );
if ( ! fadt ) {
DBGC ( colour, "ACPI could not find FADT\n" );
return -ENOENT;
}
/* Read FADT */
copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) );
pm1a_cnt_blk = le32_to_cpu ( fadtab.pm1a_cnt_blk );
pm1b_cnt_blk = le32_to_cpu ( fadtab.pm1b_cnt_blk );
pm1a_cnt = ( pm1a_cnt_blk + ACPI_PM1_CNT );
pm1b_cnt = ( pm1b_cnt_blk + ACPI_PM1_CNT );
/* Extract \_S5 from DSDT or any SSDT */
if ( ( rc = acpi_extract ( S5_SIGNATURE, &s5,
acpi_extract_sx ) ) != 0 ) {
DBGC ( colour, "ACPI could not extract \\_S5: %s\n",
strerror ( rc ) );
return rc;
}
/* Power off system */
if ( pm1a_cnt_blk ) {
slp_typa = ( ( s5 >> 0 ) & 0xff );
DBGC ( colour, "ACPI PM1a sleep type %#x => %04x\n",
slp_typa, pm1a_cnt );
outw ( ( ACPI_PM1_CNT_SLP_TYP ( slp_typa ) |
ACPI_PM1_CNT_SLP_EN ), pm1a_cnt );
}
if ( pm1b_cnt_blk ) {
slp_typb = ( ( s5 >> 8 ) & 0xff );
DBGC ( colour, "ACPI PM1b sleep type %#x => %04x\n",
slp_typb, pm1b_cnt );
outw ( ( ACPI_PM1_CNT_SLP_TYP ( slp_typb ) |
ACPI_PM1_CNT_SLP_EN ), pm1b_cnt );
}
/* On some systems, execution will continue briefly. Delay to
* avoid potentially confusing log messages.
*/
mdelay ( 1000 );
DBGC ( colour, "ACPI power off failed\n" );
return -EPROTO;
}