summaryrefslogtreecommitdiff
path: root/sys/src/cmd/scram.c
blob: d35d48235594538e3c336e4f446308d132228f67 (plain)
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
#include <u.h>
#include </386/include/ureg.h>
#include <libc.h>
#include <aml.h>

int fd, iofd;
struct Ureg u;
ulong PM1a_CNT_BLK, PM1b_CNT_BLK, SLP_TYPa, SLP_TYPb;
enum {
	SLP_EN = 0x2000,
	SLP_TM = 0x1c00,
};

typedef struct Tbl Tbl;
struct Tbl {
	uchar	sig[4];
	uchar	len[4];
	uchar	rev;
	uchar	csum;
	uchar	oemid[6];
	uchar	oemtid[8];
	uchar	oemrev[4];
	uchar	cid[4];
	uchar	crev[4];
	uchar	data[];
};

enum {
	Tblsz	= 4+4+1+1+6+8+4+4+4,
};

static ulong
get32(uchar *p){
	return p[3]<<24 | p[2]<<16 | p[1]<<8 | p[0];
}

void
apm(void)
{
	seek(fd, 0, 0);
	if(write(fd, &u, sizeof u) < 0)
		sysfatal("write: %r");
	seek(fd, 0, 0);
	if(read(fd, &u, sizeof u) < 0)
		sysfatal("read: %r");
	if(u.flags & 1)
		sysfatal("apm: %lux", (u.ax>>8) & 0xFF);
}

int
loadacpi(void)
{
	void *r, **rr;
	ulong l;
	Tbl *t;
	int n;

	amlinit();
	for(;;){
		t = malloc(sizeof(*t));
		if((n = readn(fd, t, Tblsz)) <= 0)
			break;
		if(n != Tblsz)
			return -1;
		l = get32(t->len);
		if(l < Tblsz)
			return -1;
		l -= Tblsz;
		t = realloc(t, sizeof(*t) + l);
		if(readn(fd, t->data, l) != l)
			return -1;
		if(memcmp("DSDT", t->sig, 4) == 0)
			amlload(t->data, l);
		else if(memcmp("SSDT", t->sig, 4) == 0)
			amlload(t->data, l);
		else if(memcmp("FACP", t->sig, 4) == 0){
			PM1a_CNT_BLK = get32(((uchar*)t) + 64);
			PM1b_CNT_BLK = get32(((uchar*)t) + 68);
		}
	}
	if(amleval(amlwalk(amlroot, "_S5"), "", &r) < 0)
		return -1;
	if(amltag(r) != 'p' || amllen(r) < 2)
		return -1;
	rr = amlval(r);
	SLP_TYPa = amlint(rr[0]);
	SLP_TYPb = amlint(rr[1]);
	return 0;
}

void
outw(long addr, ushort val)
{
	uchar buf[2];

	if(addr == 0)
		return;
	buf[0] = val;
	buf[1] = val >> 8;
	pwrite(iofd, buf, 2, addr);
}

void
wirecpu0(void)
{
	char buf[128];
	int ctl;

	snprint(buf, sizeof(buf), "/proc/%d/ctl", getpid());
	if((ctl = open(buf, OWRITE)) < 0){
		snprint(buf, sizeof(buf), "#p/%d/ctl", getpid());
		if((ctl = open(buf, OWRITE)) < 0)
			return;
	}
	write(ctl, "wired 0", 7);
	close(ctl);
}

void
main()
{
	wirecpu0();

	if((fd = open("/dev/apm", ORDWR)) < 0)
		if((fd = open("#P/apm", ORDWR)) < 0)
			goto tryacpi;

	u.ax = 0x530E;
	u.bx = 0x0000;
	u.cx = 0x0102;
	apm();
	u.ax = 0x5307;
	u.bx = 0x0001;
	u.cx = 0x0003;
	apm();
	
tryacpi:
	if((fd = open("/dev/acpitbls", OREAD)) < 0)
		if((fd = open("#P/acpitbls", OREAD)) < 0)
			goto fail;
	if((iofd = open("/dev/iow", OWRITE)) < 0)
		if((iofd = open("#P/iow", OWRITE)) < 0)
			goto fail;
	if(loadacpi() < 0)
		goto fail;

	outw(PM1a_CNT_BLK, ((SLP_TYPa << 10) & SLP_TM) | SLP_EN);
	outw(PM1b_CNT_BLK, ((SLP_TYPb << 10) & SLP_TM) | SLP_EN);
	sleep(100);

	/*
	 * The SetSystemSleeping() example from the ACPI spec 
	 * writes the same value in both registers. But Linux/BSD
	 * write distinct values from the _Sx package (like the
	 * code above). The _S5 package on a HP DC5700 is
	 * Package(0x2){0x0, 0x7} and writing SLP_TYPa of 0 to
	 * PM1a_CNT_BLK seems to have no effect but 0x7 seems
	 * to work fine. So trying the following as a last effort.
	 */
	SLP_TYPa |= SLP_TYPb;
	outw(PM1a_CNT_BLK, ((SLP_TYPa << 10) & SLP_TM) | SLP_EN);
	outw(PM1b_CNT_BLK, ((SLP_TYPa << 10) & SLP_TM) | SLP_EN);
	sleep(100);

fail:
	exits("scram");
}