#include #include #include #include int fd, iofd; struct Ureg u; ulong PM1a_CNT_BLK, PM1b_CNT_BLK, SLP_TYPa, SLP_TYPb; ulong GPE0_BLK, GPE1_BLK, GPE0_BLK_LEN, GPE1_BLK_LEN; 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){ amlintmask = (~0ULL) >> (t->rev <= 1)*32; 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); GPE0_BLK = get32(((uchar*)t) + 80); GPE1_BLK = get32(((uchar*)t) + 84); GPE0_BLK_LEN = *(((uchar*)t) + 92); GPE1_BLK_LEN = *(((uchar*)t) + 93); } } 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() { int n; 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; /* disable GPEs */ for(n = 0; GPE0_BLK > 0 && n < GPE0_BLK_LEN/2; n += 2){ outw(GPE0_BLK + GPE0_BLK_LEN/2 + n, 0); /* EN */ outw(GPE0_BLK + n, 0xffff); /* STS */ } for(n = 0; GPE1_BLK > 0 && n < GPE1_BLK_LEN/2; n += 2){ outw(GPE1_BLK + GPE1_BLK_LEN/2 + n, 0); /* EN */ outw(GPE1_BLK + n, 0xffff); /* STS */ } 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"); }