blob: a4bca3039f005b7394fd54da0512af86d844ca58 [file] [log] [blame]
Arthur Heymansa29498f2017-01-24 20:27:43 +01001/*
2 * Copyright (C) 2011 Peter Stuge <peter@stuge.se>
3 * Copyright (C) 2017-2018 Arthur Heymans <arthur@aheymans.xyz>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 2 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include <getopt.h>
16#include <stdio.h>
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <fcntl.h>
20#include <stdlib.h>
21#include <stdint.h>
22#include <string.h>
23#include <errno.h>
24#include <unistd.h>
25#include <sys/mman.h>
26#if defined(__GLIBC__)
27#include <sys/io.h>
28#endif
29#include <pci/pci.h>
30
31#if defined(__sun) && (defined(__i386) || defined(__amd64))
32#define MEM_DEV "/dev/xsvc"
33#else
34#define MEM_DEV "/dev/mem"
35#endif
36
37#define BUC_OFFSET 0x3414
38#define GCS_OFFSET 0x3410
39
40#define SPIBAR 0x3800
41#define FDOC 0xb0
42#define FDOD 0xb4
43
44enum {
45 DESCRIPTOR_MAP_SECTION = 0,
46 COMPONENT_SECTION,
47 REGION_SECTION,
48 MASTER_SECTION,
49 PCHSTRAPS_SECTION,
50};
51
52enum {
53 BUCTS_UNSET = 0,
54 BUCTS_SET
55};
56
57static int fd_mem = -1;
58
59static void print_usage(const char *name)
60{
61 printf("usage: %s [-h?]\n", name);
62 printf("\n"
63 " -s | --set: Set the top swap bit to invert the bootblock region decoding\n"
64 " -u | --unset: Unset the top swap bit to have regular bootblock region decoding\n"
65 " -p | --print: print the current status of the hardware\n"
66 " -h | --help: print this help\n"
67 "\n");
68}
69
70static void *sys_physmap(unsigned long phys_addr, size_t len)
71{
72 void *virt_addr =
73 mmap(0, len, PROT_WRITE | PROT_READ, MAP_SHARED, fd_mem,
74 (off_t) phys_addr);
75 return virt_addr == MAP_FAILED ? NULL : virt_addr;
76}
77
78static void physunmap(void *virt_addr, size_t len)
79{
80 if (!len) {
81 printf("Not unmapping zero size at %p\n", virt_addr);
82 return;
83 }
84 munmap(virt_addr, len);
85}
86
87static void *physmap(const char *descr, unsigned long phys_addr, size_t len)
88{
89 void *virt_addr;
90
91 if (!descr)
92 descr = "memory";
93
94 if (!len) {
95 printf("Not mapping %s, zero size at 0x%08lx.\n", descr,
96 phys_addr);
97 return NULL;
98 }
99
100 if ((getpagesize() - 1) & len)
101 fprintf(stderr, "Unaligned size 0x%lx for %s at 0x%08lx!\n",
102 (unsigned long)len, descr, phys_addr);
103
104 if ((getpagesize() - 1) & phys_addr)
105 fprintf(stderr, "Unaligned address 0x%08lx for %s!\n",
106 phys_addr, descr);
107
108 virt_addr = sys_physmap(phys_addr, len);
109 if (!virt_addr) {
110 fprintf(stderr, "Error accessing 0x%lx bytes %s at 0x%08lx!\n",
111 (unsigned long)len, descr, phys_addr);
112 perror("mmap(" MEM_DEV ")");
113 if (errno == EINVAL) {
114 fprintf(stderr, "\n");
115 fprintf(stderr,
116 "In Linux this error can be caused by the CONFIG_NONPROMISC_DEVMEM (<2.6.27),\n");
117 fprintf(stderr,
118 "CONFIG_STRICT_DEVMEM (>=2.6.27) and CONFIG_X86_PAT kernel options.\n");
119 fprintf(stderr,
120 "Please check if either is enabled in your kernel before reporting a failure.\n");
121 fprintf(stderr,
122 "You can override CONFIG_X86_PAT at boot with the nopat kernel parameter but\n");
123 fprintf(stderr,
124 "disabling the other option unfortunately requires a kernel recompile. Sorry!\n");
125 }
126 }
127
128 return virt_addr;
129}
130
131static int get_platform_info(struct pci_dev *sb, int *has_var_bb,
132 uint32_t *rcba_addr)
133{
134 *has_var_bb = 0;
135 switch (sb->device_id) {
136 case 0x1c44: /* Z68 */
137 case 0x1c46: /* P67 */
138 case 0x1c47: /* UM67 */
139 case 0x1c49: /* HM68 */
140 case 0x1c4a: /* H67 */
141 case 0x1c4b: /* HM67 */
142 case 0x1c4c: /* Q65 */
143 case 0x1c4d: /* QS67 */
144 case 0x1c4e: /* Q67 */
145 case 0x1c4f: /* QM67 */
146 case 0x1c50: /* B65 */
147 case 0x1c52: /* C202 */
148 case 0x1c54: /* C204 */
149 case 0x1c56: /* C026 */
150 case 0x1c5c: /* H61 */
151 case 0x1d40: /* C60x/X79 */
152 case 0x1c41: /* C60x/X79 */
153 case 0x1e44: /* Z77 */
154 case 0x1e46: /* Z75 */
155 case 0x1e47: /* Q77 */
156 case 0x1e48: /* Q75 */
157 case 0x1e49: /* B75 */
158 case 0x1e4a: /* H77 */
159 case 0x1e53: /* C216 */
160 case 0x1e55: /* QM77 */
161 case 0x1e56: /* QS77 */
162 case 0x1e57: /* HM77 */
163 case 0x1e58: /* UM77 */
164 case 0x1e59: /* HM76 */
165 case 0x1e5d: /* HM75 */
166 case 0x1e5e: /* HM70 */
167 case 0x1e5f: /* NM70 */
168 case 0x3b02: /* P55 */
169 case 0x3b03: /* PM55 */
170 case 0x3b06: /* H55 */
171 case 0x3b07: /* QM55 */
172 case 0x3b08: /* H57 */
173 case 0x3b09: /* HM55 */
174 case 0x3b0a: /* Q57 */
175 case 0x3b0b: /* HM57 */
176 case 0x3b0d: /* PCH 3400 Mobile SFF */
177 case 0x3b0e: /* B55 */
178 case 0x3b0f: /* QS57 */
179 case 0x3b12: /* PCH 3400 */
180 case 0x3b14: /* PCH 3420 */
181 case 0x3b16: /* PCH 3450 */
182 case 0x3b1e: /* B55 */
183 case 0x8c40: /* Lynx Point */
184 case 0x8c41: /* Lynx Point Mobile Eng. Sample */
185 case 0x8c42: /* Lynx Point Desktop Eng. Sample */
186 case 0x8c43: /* Lynx Point */
187 case 0x8c44: /* Z87 */
188 case 0x8c45: /* Lynx Point */
189 *has_var_bb = 1;
190 /* fallthrough */
191 case 0x2640: /* ICH6/ICH6R */
192 case 0x2641: /* ICH6-M */
193 case 0x2642: /* ICH6W/ICH6RW */
194 case 0x2670: /* 631xESB/632xESB/3100 */
195 case 0x27b0: /* ICH7DH */
196 case 0x27b8: /* ICH7/ICH7R */
197 case 0x27b9: /* ICH7M */
198 case 0x27bd: /* ICH7MDH */
199 case 0x27bc: /* NM10 */
200 case 0x2810: /* ICH8/ICH8R */
201 case 0x2811: /* ICH8M-E */
202 case 0x2812: /* ICH8DH */
203 case 0x2814: /* ICH8DO */
204 case 0x2815: /* ICH8M */
205 case 0x2910: /* ICH9 Eng. Sample */
206 case 0x2912: /* ICH9DH */
207 case 0x2914: /* ICH9DO */
208 case 0x2916: /* ICH9R */
209 case 0x2917: /* ICH9M-E */
210 case 0x2918: /* ICH9 */
211 case 0x2919: /* ICH9M */
212 case 0x3a10: /* ICH10R Eng. Sample */
213 case 0x3a14: /* ICH10D0 */
214 case 0x3a16: /* ICH10R */
215 case 0x3a18: /* ICH10 */
216 case 0x3a1a: /* ICH10D */
217 case 0x3a1e: /* ICH10 Eng. Sample*/
218 case 0x3b00: /* PCH 3400 Desktop */
219 case 0x3b01: /* PCH 3400 Mobile */
220 *rcba_addr = pci_read_long(sb, 0xf0) & ~1;
221 return 0;
222 default:
223 fprintf(stderr, "Unsupported LPC bridge. Sorry.\n");
224 return 1;
225 }
226}
227
228int print_status(uint32_t rcba_addr, int has_var_bb)
229{
230 int ret = 0;
231 volatile uint8_t *rcba;
232 int bild, ts, ts_size = 64 << 10;
233 rcba = physmap("RCBA", rcba_addr, 0x4000);
234 if (!rcba)
235 return 1;
236
237 bild = rcba[GCS_OFFSET] & 1;
238 ts = rcba[BUC_OFFSET] & 1;
239 int spi_is_bootdevice =
240 ((*(uint16_t *)(rcba + GCS_OFFSET) >> 10) & 3) == 3;
241
242 if (has_var_bb && spi_is_bootdevice) {
243 *(uint32_t *)(rcba + SPIBAR + FDOC) =
244 (PCHSTRAPS_SECTION << 12) | 0;
245 uint32_t pchstrap0 = *(uint32_t *)(rcba + SPIBAR + FDOD);
246 switch (pchstrap0 >> 29) {
247 case 0:
248 ts_size = 64 << 10;
249 break;
250 case 1:
251 ts_size = 128 << 10;
252 break;
253 case 2:
254 ts_size = 256 << 10;
255 break;
256 default:
257 printf("Unknown BIOS Boot-Block size (BBBS)\n");
258 ret = 1;
259 goto out;
260 }
261 }
262
263 printf("\nSystem status\n=============\n");
264 printf("BILD (BIOS Interface Lock-Down) : %s\n", bild
265 ? "set, changing top swap not possible" : "unset, changing top swap is possible");
266 printf("Top Swap : %s\n", ts ? "Enabled" : "Disabled");
267 printf("BIOS Boot-block Size (BBDS) : %dK\n", ts_size >> 10);
268 if (ts) {
269 printf("Ranges swapped : 0x%llx-0x%08llx <-> 0x%08llx-0x%08llx",
270 ((4ULL << 30) - ts_size),
271 (4ULL << 30) - 1,
272 (4ULL << 30) - 2 * ts_size,
273 (4ULL << 30) - ts_size - 1);
274 }
275
276out:
277 physunmap((void *)rcba, 0x4000);
278 return ret;
279}
280
281static int set_bucts(uint32_t rcba_addr, int bucts_state)
282{
283 int ret = 0;
284 volatile uint8_t *rcba;
285 int bild;
286 rcba = physmap("RCBA", rcba_addr, 0x4000);
287 if (!rcba)
288 return 1;
289
290 bild = rcba[GCS_OFFSET] & 1;
291 if (bild) {
292 printf("BIOS Interface Lock-Down set, Changing Top Swap not possible.\n");
293 ret = 1;
294 goto out;
295 }
296
297
298 rcba[BUC_OFFSET] = bucts_state & 1;
299 if ((rcba[BUC_OFFSET] & 1) != bucts_state) {
300 fprintf(stderr, "Hu?! setting the top swap bit failed!\n");
301 ret = 1;
302 } else {
303 printf("Setting top swap bit succeeded.\n");
304 ret = 0;
305 }
306
307out:
308 physunmap((void *)rcba, 0x4000);
309 return ret;
310}
311
312int main(int argc, char *argv[], const char *envp[])
313{
314 struct pci_access *pacc;
315 struct pci_dev *dev, *sb = NULL;
316#if defined(__FreeBSD__)
317 int io_fd;
318#endif
319 int opt, option_index = 0;
320 int print_state = 0;
321 int change_state = 0;
322 int bucts_state;
323
324 static struct option long_options[] = {
325 {"unset", 0, 0, 'u'},
326 {"set", 0, 0, 's'},
327 {"print", 0, 0, 'p'},
328 {"help", 0, 0, 'h'},
329 {0, 0, 0, 0}
330 };
331
332
333 printf("bucts utility version '" VERSION "'\n");
334
335 if (argv[1] == NULL) {
336 print_usage(argv[0]);
337 exit(1);
338 }
339
340 while ((opt = getopt_long(argc, argv, "usph?:",
341 long_options, &option_index)) != EOF) {
342 switch (opt) {
343 case 'u':
344 if (change_state) {
345 printf("Invalid setting: multiple set/unset arguments\n");
346 exit(1);
347 }
348 if (print_state) {
349 printf("Print and Set are mutually exclusive!\n");
350 exit(1);
351 }
352 bucts_state = BUCTS_UNSET;
353 change_state = 1;
354 break;
355 case 's':
356 if (change_state) {
357 printf("Invalid setting: multiple set/unset arguments\n");
358 exit(1);
359 }
360 if (print_state) {
361 printf("Print and Set are mutually exclusive!\n");
362 exit(1);
363 }
364 bucts_state = BUCTS_SET;
365 change_state = 1;
366 break;
367 case 'p':
368 if (change_state) {
369 printf("Print and Set are mutually exclusive!\n");
370 exit(1);
371 }
372 print_state = 1;
373 break;
374 case 'h':
375 print_usage(argv[0]);
376 exit(0);
377 case '?':
378 default:
379 print_usage(argv[0]);
380 exit(1);
381 }
382 }
383
384 if (optind < argc) {
385 fprintf(stderr, "Error: Extra parameter found.\n");
386 print_usage(argv[0]);
387 exit(1);
388 }
389
390
391#if defined(__FreeBSD__)
392 if ((io_fd = open("/dev/io", O_RDWR)) < 0) {
393 perror("open(/dev/io)");
394#else
395 if (iopl(3)) {
396 perror("iopl");
397#endif
398 printf("You need to be root.\n");
399 return 1;
400 }
401
402 if ((fd_mem = open(MEM_DEV, O_RDWR | O_SYNC)) == -1) {
403 perror("Error: open(" MEM_DEV ")");
404 return 1;
405 }
406
407 pacc = pci_alloc();
408 pci_init(pacc);
409 pci_scan_bus(pacc);
410 for (dev = pacc->devices; dev && !sb; dev = dev->next) {
411 pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS);
412 if (dev->vendor_id != 0x8086 || dev->device_class != 0x0601)
413 continue;
414 sb = dev;
415 }
416 if (!sb) {
417 fprintf(stderr, "Error: LPC bridge not found!\n");
418 return 1;
419 }
420
421 printf("Using LPC bridge %04x:%04x at %02x%02x:%02x.%02x\n",
422 sb->vendor_id, sb->device_id, sb->domain, sb->bus, sb->dev,
423 sb->func);
424
425 int has_var_bb;
426 uint32_t rcba_addr;
427
428 if (get_platform_info(sb, &has_var_bb, &rcba_addr))
429 exit(1);
430
431 if (print_state)
432 print_status(rcba_addr, has_var_bb);
433 if (change_state) {
434 set_bucts(rcba_addr, bucts_state);
435 print_status(rcba_addr, has_var_bb);
436 }
437
438 close(fd_mem);
439 return 0;
440}