apm.c 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*
  2. * Interface to Advanced Power Management 1.2 BIOS
  3. *
  4. * This is, in many ways, a giant hack, and when things settle down
  5. * a bit and standardize, hopefully we can write a driver that deals
  6. * more directly with the hardware and thus might be a bit cleaner.
  7. *
  8. * ACPI might be the answer, but at the moment this is simpler
  9. * and more widespread.
  10. */
  11. #include "u.h"
  12. #include "../port/lib.h"
  13. #include "mem.h"
  14. #include "dat.h"
  15. #include "fns.h"
  16. #include "io.h"
  17. #include "ureg.h"
  18. extern int apmfarcall(ushort, ulong, Ureg*); /* apmjump.s */
  19. static int
  20. getreg(ulong *reg, ISAConf *isa, char *name)
  21. {
  22. int i;
  23. int nl;
  24. nl = strlen(name);
  25. for(i=0; i<isa->nopt; i++){
  26. if(cistrncmp(isa->opt[i], name, nl)==0 && isa->opt[i][nl] == '='){
  27. *reg = strtoul(isa->opt[i]+nl+1, nil, 16);
  28. return 0;
  29. }
  30. }
  31. return -1;
  32. }
  33. /*
  34. * Segment descriptors look like this.
  35. *
  36. * d1: [base 31:24] [gran] [is32bit] [0] [unused] [limit 19:16]
  37. [present] [privlev] [type 3:0] [base 23:16]
  38. * d0: [base 15:00] [limit 15:00]
  39. *
  40. * gran is 0 for 1-byte granularity, 1 for 4k granularity
  41. * type is 0 for system segment, 1 for code/data.
  42. *
  43. * clearly we know way too much about the memory unit.
  44. * however, knowing this much about the memory unit
  45. * means that the memory unit need not know anything
  46. * about us.
  47. *
  48. * what a crock.
  49. */
  50. static void
  51. setgdt(int sel, ulong base, ulong limit, int flag)
  52. {
  53. if(sel < 0 || sel >= NGDT)
  54. panic("setgdt");
  55. base = (ulong)KADDR(base);
  56. m->gdt[sel].d0 = (base<<16) | (limit&0xFFFF);
  57. m->gdt[sel].d1 = (base&0xFF000000) | (limit&0x000F0000) |
  58. ((base>>16)&0xFF) | SEGP | SEGPL(0) | flag;
  59. }
  60. static ulong ax, cx, dx, di, ebx, esi;
  61. static Ureg apmu;
  62. static long
  63. apmread(Chan*, void *a, long n, vlong off)
  64. {
  65. if(off < 0)
  66. error("badarg");
  67. if(n+off > sizeof apmu)
  68. n = sizeof apmu - off;
  69. if(n <= 0)
  70. return 0;
  71. memmove(a, (char*)&apmu+off, n);
  72. return n;
  73. }
  74. static long
  75. apmwrite(Chan*, void *a, long n, vlong off)
  76. {
  77. int s;
  78. if(off || n != sizeof apmu)
  79. error("write a Ureg");
  80. memmove(&apmu, a, sizeof apmu);
  81. s = splhi();
  82. apmfarcall(APMCSEL, ebx, &apmu);
  83. splx(s);
  84. return n;
  85. }
  86. void
  87. apmlink(void)
  88. {
  89. ISAConf isa;
  90. char *s;
  91. if(isaconfig("apm", 0, &isa) == 0)
  92. return;
  93. /* XXX use realmode() */
  94. /*
  95. * APM info passed from boot loader.
  96. * Now we need to set up the GDT entries for APM.
  97. *
  98. * AX = 32-bit code segment base address
  99. * EBX = 32-bit code segment offset
  100. * CX = 16-bit code segment base address
  101. * DX = 32-bit data segment base address
  102. * ESI = <16-bit code segment length> <32-bit code segment length> (hi then lo)
  103. * DI = 32-bit data segment length
  104. */
  105. if(getreg(&ax, &isa, s="ax") < 0
  106. || getreg(&ebx, &isa, s="ebx") < 0
  107. || getreg(&cx, &isa, s="cx") < 0
  108. || getreg(&dx, &isa, s="dx") < 0
  109. || getreg(&esi, &isa, s="esi") < 0
  110. || getreg(&di, &isa, s="di") < 0){
  111. print("apm: missing register %s\n", s);
  112. return;
  113. }
  114. /*
  115. * The NEC Versa SX bios does not report the correct 16-bit code
  116. * segment length when loaded directly from mbr -> 9load (as compared
  117. * with going through ld.com). We'll make both code segments 64k-1 bytes.
  118. */
  119. esi = 0xFFFFFFFF;
  120. /*
  121. * We are required by the BIOS to set up three consecutive segments,
  122. * one for the APM 32-bit code, one for the APM 16-bit code, and
  123. * one for the APM data. The BIOS handler uses the code segment it
  124. * get called with to determine the other two segment selector.
  125. */
  126. setgdt(APMCSEG, ax<<4, ((esi&0xFFFF)-1)&0xFFFF, SEGEXEC|SEGR|SEGD);
  127. setgdt(APMCSEG16, cx<<4, ((esi>>16)-1)&0xFFFF, SEGEXEC|SEGR);
  128. setgdt(APMDSEG, dx<<4, (di-1)&0xFFFF, SEGDATA|SEGW|SEGD);
  129. addarchfile("apm", 0660, apmread, apmwrite);
  130. print("apm0: configured cbase %.8lux off %.8lux\n", ax<<4, ebx);
  131. return;
  132. }