xref: /aosp_15_r20/external/coreboot/util/intelmetool/me.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <pci/pci.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <sys/io.h>
8 #include <assert.h>
9 #include <unistd.h>
10 
11 #include "intelmetool.h"
12 #include "me.h"
13 #include "mmap.h"
14 
15 #define read32(addr, off) ( *((uint32_t *) (addr + off)) )
16 #define write32(addr, off, val) ( *((uint32_t *) (addr + off)) = val)
17 
18 /* Path that the BIOS should take based on ME state */
19 /*
20 static const char *me_bios_path_values[] = {
21 	[ME_NORMAL_BIOS_PATH]		= "Normal",
22 	[ME_S3WAKE_BIOS_PATH]		= "S3 Wake",
23 	[ME_ERROR_BIOS_PATH]		= "Error",
24 	[ME_RECOVERY_BIOS_PATH]		= "Recovery",
25 	[ME_DISABLE_BIOS_PATH]		= "Disable",
26 	[ME_FIRMWARE_UPDATE_BIOS_PATH]	= "Firmware Update",
27 };
28 */
29 
30 /* MMIO base address for MEI interface */
31 static uint32_t mei_base_address;
32 static uint8_t* mei_mmap;
33 
mei_dump(void * ptr,int dword,int offset,const char * type)34 static void mei_dump(void *ptr, int dword, int offset, const char *type)
35 {
36 	/* struct mei_csr *csr; */
37 
38 
39 	switch (offset) {
40 	case MEI_H_CSR:
41 	case MEI_ME_CSR_HA:
42 /*
43 		csr = ptr;
44 		if (!csr) {
45 		printf("%-9s[%02x] : ", type, offset);
46 			printf("ERROR: 0x%08x\n", dword);
47 			break;
48 		}
49 		printf("%-9s[%02x] : ", type, offset);
50 		printf("depth=%u read=%02u write=%02u ready=%u "
51 		       "reset=%u intgen=%u intstatus=%u intenable=%u\n",
52 		       csr->buffer_depth, csr->buffer_read_ptr,
53 		       csr->buffer_write_ptr, csr->ready, csr->reset,
54 		       csr->interrupt_generate, csr->interrupt_status,
55 		       csr->interrupt_enable);
56 */
57 		break;
58 	case MEI_ME_CB_RW:
59 	case MEI_H_CB_WW:
60 		printf("%-9s[%02x] : ", type, offset);
61 		printf("CB: 0x%08x\n", dword);
62 		break;
63 	default:
64 		printf("%-9s[%02x] : ", type, offset);
65 		printf("0x%08x\n", offset);
66 		break;
67 	}
68 }
69 
70 /*
71  * ME/MEI access helpers using memcpy to avoid aliasing.
72  */
73 
mei_read_dword_ptr(void * ptr,uint32_t offset)74 static inline void mei_read_dword_ptr(void *ptr, uint32_t offset)
75 {
76 	uint32_t dword = read32(mei_mmap, offset);
77 	memcpy(ptr, &dword, sizeof(dword));
78 
79 	if (debug) {
80 		mei_dump(ptr, dword, offset, "READ");
81 	}
82 }
83 
mei_write_dword_ptr(void * ptr,uint32_t offset)84 static inline void mei_write_dword_ptr(void *ptr, uint32_t offset)
85 {
86 	uint32_t dword = 0;
87 	memcpy(&dword, ptr, sizeof(dword));
88 	write32(mei_mmap, offset, dword);
89 
90 	if (debug) {
91 		mei_dump(ptr, dword, offset, "WRITE");
92 	}
93 }
94 
pci_read_dword_ptr(struct pci_dev * dev,void * ptr,uint32_t offset)95 static inline void pci_read_dword_ptr(struct pci_dev *dev, void *ptr, uint32_t offset)
96 {
97 	uint32_t dword = pci_read_long(dev, offset);
98 	memcpy(ptr, &dword, sizeof(dword));
99 
100 	if (debug) {
101 		mei_dump(ptr, dword, offset, "PCI READ");
102 	}
103 }
104 
read_host_csr(struct mei_csr * csr)105 static inline void read_host_csr(struct mei_csr *csr)
106 {
107 	mei_read_dword_ptr(csr, MEI_H_CSR);
108 }
109 
write_host_csr(struct mei_csr * csr)110 static inline void write_host_csr(struct mei_csr *csr)
111 {
112 	mei_write_dword_ptr(csr, MEI_H_CSR);
113 }
114 
read_me_csr(struct mei_csr * csr)115 static inline void read_me_csr(struct mei_csr *csr)
116 {
117 	mei_read_dword_ptr(csr, MEI_ME_CSR_HA);
118 }
119 
write_cb(uint32_t dword)120 static inline void write_cb(uint32_t dword)
121 {
122 	write32(mei_mmap, MEI_H_CB_WW, dword);
123 
124 	if (debug) {
125 		mei_dump(NULL, dword, MEI_H_CB_WW, "WRITE");
126 	}
127 }
128 
read_cb(void)129 static inline uint32_t read_cb(void)
130 {
131 	uint32_t dword = read32(mei_mmap, MEI_ME_CB_RW);
132 
133 	if (debug) {
134 		mei_dump(NULL, dword, MEI_ME_CB_RW, "READ");
135 	}
136 
137 	return dword;
138 }
139 
140 /* Wait for ME ready bit to be asserted */
mei_wait_for_me_ready(void)141 static int mei_wait_for_me_ready(void)
142 {
143 	struct mei_csr me;
144 	unsigned try = ME_RETRY;
145 
146 	while (try--) {
147 		read_me_csr(&me);
148 		if (me.ready)
149 			return 0;
150 		usleep(ME_DELAY);
151 	}
152 
153 	printf("ME: failed to become ready\n");
154 	return -1;
155 }
156 
mei_reset(void)157 void mei_reset(void)
158 {
159 	struct mei_csr host;
160 
161 	if (mei_wait_for_me_ready() < 0)
162 		return;
163 
164 	/* Reset host and ME circular buffers for next message */
165 	read_host_csr(&host);
166 	host.reset = 1;
167 	host.interrupt_generate = 1;
168 	write_host_csr(&host);
169 
170 	if (mei_wait_for_me_ready() < 0)
171 		return;
172 
173 	/* Re-init and indicate host is ready */
174 	read_host_csr(&host);
175 	host.interrupt_generate = 1;
176 	host.ready = 1;
177 	host.reset = 0;
178 	write_host_csr(&host);
179 }
180 
mei_send_msg(struct mei_header * mei,struct mkhi_header * mkhi,void * req_data)181 static int mei_send_msg(struct mei_header *mei, struct mkhi_header *mkhi,
182 			void *req_data)
183 {
184 	struct mei_csr host;
185 	unsigned ndata , n;
186 	uint32_t *data;
187 
188 	/* Number of dwords to write, ignoring MKHI */
189 	ndata = (mei->length) >> 2;
190 
191 	/* Pad non-dword aligned request message length */
192 	if (mei->length & 3)
193 		ndata++;
194 	if (!ndata) {
195 		printf("ME: request does not include MKHI\n");
196 		return -1;
197 	}
198 	ndata++; /* Add MEI header */
199 
200 	/*
201 	 * Make sure there is still room left in the circular buffer.
202 	 * Reset the buffer pointers if the requested message will not fit.
203 	 */
204 	read_host_csr(&host);
205 	if ((host.buffer_depth - host.buffer_write_ptr) < ndata) {
206 		printf("ME: circular buffer full, resetting...\n");
207 		mei_reset();
208 		read_host_csr(&host);
209 	}
210 
211 	/*
212 	 * This implementation does not handle splitting large messages
213 	 * across multiple transactions.  Ensure the requested length
214 	 * will fit in the available circular buffer depth.
215 	 */
216 	if ((host.buffer_depth - host.buffer_write_ptr) < ndata) {
217 		printf("ME: message (%u) too large for buffer (%u)\n",
218 		       ndata + 2, host.buffer_depth);
219 		return -1;
220 	}
221 
222 	/* Write MEI header */
223 	mei_write_dword_ptr(mei, MEI_H_CB_WW);
224 	ndata--;
225 
226 	/* Write MKHI header */
227 	mei_write_dword_ptr(mkhi, MEI_H_CB_WW);
228 	ndata--;
229 
230 	/* Write message data */
231 	data = req_data;
232 	for (n = 0; n < ndata; ++n)
233 		write_cb(*data++);
234 
235 	/* Generate interrupt to the ME */
236 	read_host_csr(&host);
237 	host.interrupt_generate = 1;
238 	write_host_csr(&host);
239 
240 	/* Make sure ME is ready after sending request data */
241 	return mei_wait_for_me_ready();
242 }
243 
mei_recv_msg(struct mei_header * mei,struct mkhi_header * mkhi,void * rsp_data,uint32_t rsp_bytes)244 static int mei_recv_msg(struct mei_header *mei, struct mkhi_header *mkhi,
245 			void *rsp_data, uint32_t rsp_bytes)
246 {
247 	struct mei_header mei_rsp;
248 	struct mkhi_header mkhi_rsp;
249 	struct mei_csr me, host;
250 	unsigned ndata, n;
251 	unsigned expected;
252 	uint32_t *data;
253 
254 	/* Total number of dwords to read from circular buffer */
255 	expected = (rsp_bytes + sizeof(mei_rsp) + sizeof(mkhi_rsp)) >> 2;
256 	if (rsp_bytes & 3)
257 		expected++;
258 
259 	if (debug) {
260 		printf("expected u32 = %d\n", expected);
261 	}
262 	/*
263 	 * The interrupt status bit does not appear to indicate that the
264 	 * message has actually been received.  Instead we wait until the
265 	 * expected number of dwords are present in the circular buffer.
266 	 */
267 	for (n = ME_RETRY; n; --n) {
268 		read_me_csr(&me);
269 		if ((me.buffer_write_ptr - me.buffer_read_ptr) >= expected)
270 		//if (me.interrupt_generate && !me.interrupt_status)
271 		//if (me.interrupt_status)
272 			break;
273 		usleep(ME_DELAY);
274 	}
275 	if (!n) {
276 		printf("ME: timeout waiting for data: expected "
277 		       "%u, available %u\n", expected,
278 		       me.buffer_write_ptr - me.buffer_read_ptr);
279 		return -1;
280 	}
281 	/* Read and verify MEI response header from the ME */
282 	mei_read_dword_ptr(&mei_rsp, MEI_ME_CB_RW);
283 	if (!mei_rsp.is_complete) {
284 		printf("ME: response is not complete\n");
285 		return -1;
286 	}
287 
288 	/* Handle non-dword responses and expect at least MKHI header */
289 	ndata = mei_rsp.length >> 2;
290 	if (mei_rsp.length & 3)
291 		ndata++;
292 	if (ndata != (expected - 1)) {  //XXX
293 		printf("ME: response is missing data\n");
294 		//return -1;
295 	}
296 
297 	/* Read and verify MKHI response header from the ME */
298 	mei_read_dword_ptr(&mkhi_rsp, MEI_ME_CB_RW);
299 	if (!mkhi_rsp.is_response ||
300 	    mkhi->group_id != mkhi_rsp.group_id ||
301 	    mkhi->command != mkhi_rsp.command) {
302 		printf("ME: invalid response, group %u ?= %u, "
303 		       "command %u ?= %u, is_response %u\n", mkhi->group_id,
304 		       mkhi_rsp.group_id, mkhi->command, mkhi_rsp.command,
305 		       mkhi_rsp.is_response);
306 		//return -1;
307 	}
308 	ndata--; /* MKHI header has been read */
309 
310 	/* Make sure caller passed a buffer with enough space */
311 	if (ndata != (rsp_bytes >> 2)) {
312 		printf("ME: not enough room in response buffer: "
313 		       "%u != %u\n", ndata, rsp_bytes >> 2);
314 		//return -1;
315 	}
316 
317 	/* Read response data from the circular buffer */
318 	data = rsp_data;
319 	for (n = 0; n < ndata; ++n)
320 		*data++ = read_cb();
321 
322 	/* Tell the ME that we have consumed the response */
323 	read_host_csr(&host);
324 	host.interrupt_status = 1;
325 	host.interrupt_generate = 1;
326 	write_host_csr(&host);
327 
328 	return mei_wait_for_me_ready();
329 }
330 
mei_sendrecv(struct mei_header * mei,struct mkhi_header * mkhi,void * req_data,void * rsp_data,uint32_t rsp_bytes)331 static inline int mei_sendrecv(struct mei_header *mei, struct mkhi_header *mkhi,
332 			       void *req_data, void *rsp_data, uint32_t rsp_bytes)
333 {
334 	if (mei_send_msg(mei, mkhi, req_data) < 0)
335 		return -1;
336 	if (mei_recv_msg(mei, mkhi, rsp_data, rsp_bytes) < 0)
337 		return -1;
338 	return 0;
339 }
340 
341 /* Send END OF POST message to the ME */
342 /*
343 static int mkhi_end_of_post(void)
344 {
345 	struct mkhi_header mkhi = {
346 		.group_id	= MKHI_GROUP_ID_GEN,
347 		.command	= MKHI_END_OF_POST,
348 	};
349 	struct mei_header mei = {
350 		.is_complete	= 1,
351 		.host_address	= MEI_HOST_ADDRESS,
352 		.client_address	= MEI_ADDRESS_MKHI,
353 		.length		= sizeof(mkhi),
354 	};
355 
356 	if (mei_sendrecv(&mei, &mkhi, NULL, NULL, 0) < 0) {
357 		printf("ME: END OF POST message failed\n");
358 		return -1;
359 	}
360 
361 	printf("ME: END OF POST message successful\n");
362 	return 0;
363 }
364 */
365 
366 /* Get ME firmware version */
mkhi_get_fw_version(int * major,int * minor)367 int mkhi_get_fw_version(int *major, int *minor)
368 {
369 	uint32_t data = 0;
370 	struct me_fw_version version = {0};
371 
372 	struct mkhi_header mkhi = {
373 		.group_id	= MKHI_GROUP_ID_GEN,
374 		.command	= GEN_GET_FW_VERSION,
375 		.is_response 	= 0,
376 	};
377 
378 	struct mei_header mei = {
379 		.is_complete	= 1,
380 		.host_address	= MEI_HOST_ADDRESS,
381 		.client_address	= MEI_ADDRESS_MKHI,
382 		.length		= sizeof(mkhi),
383 	};
384 
385 #ifndef OLDARC
386 	/* Send request and wait for response */
387 	if (mei_sendrecv(&mei, &mkhi, &data, &version, sizeof(version) ) < 0) {
388 		printf("ME: GET FW VERSION message failed\n");
389 		return -1;
390 	}
391 	printf("ME: Firmware Version %u.%u.%u.%u (code) "
392 	       "%u.%u.%u.%u (recovery) "
393 	       "%u.%u.%u.%u (fitc)\n\n",
394 	       version.code_major, version.code_minor,
395 	       version.code_build_number, version.code_hot_fix,
396 	       version.recovery_major, version.recovery_minor,
397 	       version.recovery_build_number, version.recovery_hot_fix,
398 	       version.fitcmajor, version.fitcminor,
399 	       version.fitcbuildno, version.fitchotfix);
400 #else
401 	/* Send request and wait for response */
402 	if (mei_sendrecv(&mei, &mkhi, &data, &version, 2*sizeof(uint32_t) ) < 0) {
403 		printf("ME: GET FW VERSION message failed\n");
404 		return -1;
405 	}
406 	printf("ME: Firmware Version %u.%u (code)\n\n",
407 	       version.code_major, version.code_minor);
408 #endif
409 	if (major)
410 		*major = version.code_major;
411 	if (minor)
412 		*minor = version.code_minor;
413 	return 0;
414 }
415 
print_cap(const char * name,int state)416 static void print_cap(const char *name, int state)
417 {
418 	printf("ME Capability: %-30s : %s\n",
419 	       name, state ? CRED "ON" RESET : CGRN "OFF" RESET);
420 }
421 
422 /* Get ME Firmware Capabilities */
mkhi_get_fwcaps(void)423 int mkhi_get_fwcaps(void)
424 {
425 	struct {
426 		uint32_t rule_id;
427 		uint32_t rule_len;
428 
429 		struct me_fwcaps cap;
430 	} fwcaps;
431 
432 	fwcaps.rule_id = 0;
433 	fwcaps.rule_len = 0;
434 
435 	struct mkhi_header mkhi = {
436 		.group_id	= MKHI_GROUP_ID_FWCAPS,
437 		.command	= MKHI_FWCAPS_GET_RULE,
438 		.is_response	= 0,
439 	};
440 	struct mei_header mei = {
441 		.is_complete	= 1,
442 		.host_address	= MEI_HOST_ADDRESS,
443 		.client_address	= MEI_ADDRESS_MKHI,
444 		.length		= sizeof(mkhi) + sizeof(fwcaps.rule_id),
445 	};
446 
447 	/* Send request and wait for response */
448 	if (mei_sendrecv(&mei, &mkhi, &fwcaps.rule_id, &fwcaps.cap, sizeof(fwcaps.cap)) < 0) {
449 		printf("ME: GET FWCAPS message failed\n");
450 		return -1;
451 	}
452 
453 	print_cap("Full Network manageability                ", fwcaps.cap.caps_sku.full_net);
454 	print_cap("Regular Network manageability             ", fwcaps.cap.caps_sku.std_net);
455 	print_cap("Manageability                             ", fwcaps.cap.caps_sku.manageability);
456 	print_cap("Small business technology                 ", fwcaps.cap.caps_sku.small_business);
457 	print_cap("Level III manageability                   ", fwcaps.cap.caps_sku.l3manageability);
458 	print_cap("IntelR Anti-Theft (AT)                    ", fwcaps.cap.caps_sku.intel_at);
459 	print_cap("IntelR Capability Licensing Service (CLS) ", fwcaps.cap.caps_sku.intel_cls);
460 	print_cap("IntelR Power Sharing Technology (MPC)     ", fwcaps.cap.caps_sku.intel_mpc);
461 	print_cap("ICC Over Clocking                         ", fwcaps.cap.caps_sku.icc_over_clocking);
462 	print_cap("Protected Audio Video Path (PAVP)         ", fwcaps.cap.caps_sku.pavp);
463 	print_cap("IPV6                                      ", fwcaps.cap.caps_sku.ipv6);
464 	print_cap("KVM Remote Control (KVM)                  ", fwcaps.cap.caps_sku.kvm);
465 	print_cap("Outbreak Containment Heuristic (OCH)      ", fwcaps.cap.caps_sku.och);
466 	print_cap("Virtual LAN (VLAN)                        ", fwcaps.cap.caps_sku.vlan);
467 	print_cap("TLS                                       ", fwcaps.cap.caps_sku.tls);
468 	print_cap("Wireless LAN (WLAN)                       ", fwcaps.cap.caps_sku.wlan);
469 
470 	return 0;
471 }
472 
473 /* Tell ME to issue a global reset */
mkhi_global_reset(void)474 uint32_t mkhi_global_reset(void)
475 {
476 	struct me_global_reset reset = {
477 		.request_origin	= GLOBAL_RESET_BIOS_POST,
478 		.reset_type	= CBM_RR_GLOBAL_RESET,
479 	};
480 	struct mkhi_header mkhi = {
481 		.group_id	= MKHI_GROUP_ID_CBM,
482 		.command	= MKHI_GLOBAL_RESET,
483 	};
484 	struct mei_header mei = {
485 		.is_complete	= 1,
486 		.length		= sizeof(mkhi) + sizeof(reset),
487 		.host_address	= MEI_HOST_ADDRESS,
488 		.client_address	= MEI_ADDRESS_MKHI,
489 	};
490 
491 	printf("ME: Requesting global reset\n");
492 
493 	/* Send request and wait for response */
494 	if (mei_sendrecv(&mei, &mkhi, &reset, NULL, 0) < 0) {
495 		/* No response means reset will happen shortly... */
496 		asm("hlt");
497 	}
498 
499 	/* If the ME responded it rejected the reset request */
500 	printf("ME: Global Reset failed\n");
501 	return -1;
502 }
503 
504 /* Tell ME thermal reporting parameters */
505 /*
506 void mkhi_thermal(void)
507 {
508 	struct me_thermal_reporting thermal = {
509 		.polling_timeout = 2,
510 		.smbus_ec_msglen = 1,
511 		.smbus_ec_msgpec = 0,
512 		.dimmnumber = 4,
513 	};
514 	struct mkhi_header mkhi = {
515 		.group_id	= MKHI_GROUP_ID_CBM,
516 		.command	= MKHI_THERMAL_REPORTING,
517 	};
518 	struct mei_header mei = {
519 		.is_complete	= 1,
520 		.length		= sizeof(mkhi) + sizeof(thermal),
521 		.host_address	= MEI_HOST_ADDRESS,
522 		.client_address	= MEI_ADDRESS_THERMAL,
523 	};
524 
525 	printf("ME: Sending thermal reporting params\n");
526 
527 	mei_sendrecv(&mei, &mkhi, &thermal, NULL, 0);
528 }
529 */
530 
531 /* Enable debug of internal ME memory */
mkhi_debug_me_memory(void * physaddr)532 int mkhi_debug_me_memory(void *physaddr)
533 {
534 	uint32_t data = 0;
535 
536 	/* copy whole ME memory to a readable space */
537 	struct me_debug_mem memory = {
538 		.debug_phys = (uintptr_t)physaddr,
539 		.debug_size = 0x2000000,
540 		.me_phys = 0x20000000,
541 		.me_size = 0x2000000,
542 	};
543 	struct mkhi_header mkhi = {
544 		.group_id	= MKHI_GROUP_ID_GEN,
545 		.command	= GEN_SET_DEBUG_MEM,
546 		.is_response	= 0,
547 	};
548 	struct mei_header mei = {
549 		.is_complete	= 1,
550 		.length		= sizeof(mkhi) + sizeof(memory),
551 		.host_address	= MEI_HOST_ADDRESS,
552 		.client_address	= MEI_ADDRESS_MKHI,
553 	};
554 
555 	printf("ME: Debug memory to 0x%zx ...", (size_t)physaddr);
556 	if (mei_sendrecv(&mei, &mkhi, &memory, &data, 0) < 0) {
557 		printf("failed\n");
558 		return -1;
559 	} else {
560 		printf("done\n");
561 	}
562 	return 0;
563 }
564 
565 /* Prepare ME for MEI messages */
intel_mei_setup(struct pci_dev * dev)566 uint32_t intel_mei_setup(struct pci_dev *dev)
567 {
568 	struct mei_csr host;
569 	uint16_t reg16;
570 	uint32_t pagerounded;
571 
572 	mei_base_address = dev->base_addr[0] & ~0xf;
573 	pagerounded = mei_base_address & ~0xfff;
574 	mei_mmap = map_physical(pagerounded, 0x2000);
575 	mei_mmap += mei_base_address - pagerounded;
576 	if (mei_mmap == NULL) {
577 		printf("Could not map ME setup memory.\n"
578 		       "Do you have kernel cmdline argument 'iomem=relaxed' set ?\n");
579 		return 1;
580 	}
581 
582 	/* Ensure Memory and Bus Master bits are set */
583 	reg16 = pci_read_word(dev, PCI_COMMAND);
584 	reg16 |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
585 	pci_write_word(dev, PCI_COMMAND, reg16);
586 
587 	/* Clean up status for next message */
588 	read_host_csr(&host);
589 	host.interrupt_generate = 1;
590 	host.ready = 1;
591 	host.reset = 0;
592 	write_host_csr(&host);
593 
594 	return 0;
595 }
596 
597 /* Read the Extend register hash of ME firmware */
intel_me_extend_valid(struct pci_dev * dev)598 int intel_me_extend_valid(struct pci_dev *dev)
599 {
600 	struct me_heres status;
601 	uint32_t extend[8] = {0};
602 	int i, count = 0;
603 
604 	pci_read_dword_ptr(dev, &status, PCI_ME_HERES);
605 	if (!status.extend_feature_present) {
606 		printf("ME: Extend Feature not present\n");
607 		return -1;
608 	}
609 
610 	if (!status.extend_reg_valid) {
611 		printf("ME: Extend Register not valid\n");
612 		return -1;
613 	}
614 
615 	switch (status.extend_reg_algorithm) {
616 	case PCI_ME_EXT_SHA1:
617 		count = 5;
618 		printf("ME: Extend SHA-1: ");
619 		break;
620 	case PCI_ME_EXT_SHA256:
621 		count = 8;
622 		printf("ME: Extend SHA-256: ");
623 		break;
624 	default:
625 		printf("ME: Extend Algorithm %d unknown\n",
626 		       status.extend_reg_algorithm);
627 		return -1;
628 	}
629 
630 	for (i = 0; i < count; ++i) {
631 		extend[i] = pci_read_long(dev, PCI_ME_HER(i));
632 		printf("%08x", extend[i]);
633 	}
634 	printf("\n");
635 
636 	return 0;
637 }
638