1/*- 2 * Copyright (c) 1997, Stefan Esser <se@freebsd.org> 3 * Copyright (c) 2000, Michael Smith <msmith@freebsd.org> 4 * Copyright (c) 2000, BSDi 5 * Copyright (c) 2004, Scott Long <scottl@freebsd.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice unmodified, this list of conditions, and the following 13 * disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include <sys/cdefs.h>
|
31__FBSDID("$FreeBSD: head/sys/i386/pci/pci_cfgreg.c 267992 2014-06-28 03:56:17Z hselasky $");
|
31__FBSDID("$FreeBSD: head/sys/i386/pci/pci_cfgreg.c 282274 2015-04-30 15:48:48Z jhb $"); |
32 33#include "opt_xbox.h" 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/bus.h> 38#include <sys/lock.h> 39#include <sys/kernel.h> 40#include <sys/mutex.h> 41#include <sys/malloc.h> 42#include <sys/queue.h> 43#include <sys/sysctl.h> 44#include <dev/pci/pcivar.h> 45#include <dev/pci/pcireg.h> 46#include <machine/pci_cfgreg.h> 47#include <machine/pc/bios.h> 48 49#include <vm/vm.h> 50#include <vm/vm_param.h> 51#include <vm/vm_kern.h> 52#include <vm/vm_extern.h> 53#include <vm/pmap.h> 54#include <machine/pmap.h> 55 56#ifdef XBOX 57#include <machine/xbox.h> 58#endif 59 60#define PRVERB(a) do { \ 61 if (bootverbose) \ 62 printf a ; \ 63} while(0) 64 65#define PCIE_CACHE 8 66struct pcie_cfg_elem { 67 TAILQ_ENTRY(pcie_cfg_elem) elem; 68 vm_offset_t vapage; 69 vm_paddr_t papage; 70}; 71 72enum { 73 CFGMECH_NONE = 0, 74 CFGMECH_1, 75 CFGMECH_2, 76 CFGMECH_PCIE, 77}; 78 79SYSCTL_DECL(_hw_pci); 80 81static TAILQ_HEAD(pcie_cfg_list, pcie_cfg_elem) pcie_list[MAXCPU]; 82static uint64_t pcie_base; 83static int pcie_minbus, pcie_maxbus; 84static uint32_t pcie_badslots; 85static int cfgmech; 86static int devmax; 87static struct mtx pcicfg_mtx; 88static int mcfg_enable = 1; 89SYSCTL_INT(_hw_pci, OID_AUTO, mcfg, CTLFLAG_RDTUN, &mcfg_enable, 0, 90 "Enable support for PCI-e memory mapped config access"); 91 92static uint32_t pci_docfgregread(int bus, int slot, int func, int reg, 93 int bytes); 94static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes); 95static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes);
|
96#ifndef XEN
|
96static int pcireg_cfgopen(void);
|
98#endif
|
97static int pciereg_cfgread(int bus, unsigned slot, unsigned func, 98 unsigned reg, unsigned bytes); 99static void pciereg_cfgwrite(int bus, unsigned slot, unsigned func, 100 unsigned reg, int data, unsigned bytes); 101 102/* 103 * Some BIOS writers seem to want to ignore the spec and put 104 * 0 in the intline rather than 255 to indicate none. Some use 105 * numbers in the range 128-254 to indicate something strange and 106 * apparently undocumented anywhere. Assume these are completely bogus 107 * and map them to 255, which means "none". 108 */ 109static __inline int 110pci_i386_map_intline(int line) 111{ 112 if (line == 0 || line >= 128) 113 return (PCI_INVALID_IRQ); 114 return (line); 115} 116
|
119#ifndef XEN
|
117static u_int16_t 118pcibios_get_version(void) 119{ 120 struct bios_regs args; 121 122 if (PCIbios.ventry == 0) { 123 PRVERB(("pcibios: No call entry point\n")); 124 return (0); 125 } 126 args.eax = PCIBIOS_BIOS_PRESENT; 127 if (bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL))) { 128 PRVERB(("pcibios: BIOS_PRESENT call failed\n")); 129 return (0); 130 } 131 if (args.edx != 0x20494350) { 132 PRVERB(("pcibios: BIOS_PRESENT didn't return 'PCI ' in edx\n")); 133 return (0); 134 } 135 return (args.ebx & 0xffff); 136}
|
140#endif
|
137 138/* 139 * Initialise access to PCI configuration space 140 */ 141int 142pci_cfgregopen(void) 143{
|
148#ifdef XEN
149 return (0);
150#else
|
144 static int opened = 0; 145 uint64_t pciebar; 146 u_int16_t vid, did; 147 u_int16_t v; 148 149 if (opened) 150 return (1); 151 152 if (cfgmech == CFGMECH_NONE && pcireg_cfgopen() == 0) 153 return (0); 154 155 v = pcibios_get_version(); 156 if (v > 0) 157 PRVERB(("pcibios: BIOS version %x.%02x\n", (v & 0xff00) >> 8, 158 v & 0xff)); 159 mtx_init(&pcicfg_mtx, "pcicfg", NULL, MTX_SPIN); 160 opened = 1; 161 162 /* $PIR requires PCI BIOS 2.10 or greater. */ 163 if (v >= 0x0210) 164 pci_pir_open(); 165 166 if (cfgmech == CFGMECH_PCIE) 167 return (1); 168 169 /* 170 * Grope around in the PCI config space to see if this is a 171 * chipset that is capable of doing memory-mapped config cycles. 172 * This also implies that it can do PCIe extended config cycles. 173 */ 174 175 /* Check for supported chipsets */ 176 vid = pci_cfgregread(0, 0, 0, PCIR_VENDOR, 2); 177 did = pci_cfgregread(0, 0, 0, PCIR_DEVICE, 2); 178 switch (vid) { 179 case 0x8086: 180 switch (did) { 181 case 0x3590: 182 case 0x3592: 183 /* Intel 7520 or 7320 */ 184 pciebar = pci_cfgregread(0, 0, 0, 0xce, 2) << 16; 185 pcie_cfgregopen(pciebar, 0, 255); 186 break; 187 case 0x2580: 188 case 0x2584: 189 case 0x2590: 190 /* Intel 915, 925, or 915GM */ 191 pciebar = pci_cfgregread(0, 0, 0, 0x48, 4); 192 pcie_cfgregopen(pciebar, 0, 255); 193 break; 194 } 195 } 196 197 return(1);
|
205#endif
|
198} 199 200static uint32_t 201pci_docfgregread(int bus, int slot, int func, int reg, int bytes) 202{ 203 204 if (cfgmech == CFGMECH_PCIE && 205 (bus >= pcie_minbus && bus <= pcie_maxbus) && 206 (bus != 0 || !(1 << slot & pcie_badslots))) 207 return (pciereg_cfgread(bus, slot, func, reg, bytes)); 208 else 209 return (pcireg_cfgread(bus, slot, func, reg, bytes)); 210} 211 212/* 213 * Read configuration space register 214 */ 215u_int32_t 216pci_cfgregread(int bus, int slot, int func, int reg, int bytes) 217{ 218 uint32_t line; 219 220 /* 221 * Some BIOS writers seem to want to ignore the spec and put 222 * 0 in the intline rather than 255 to indicate none. The rest of 223 * the code uses 255 as an invalid IRQ. 224 */ 225 if (reg == PCIR_INTLINE && bytes == 1) { 226 line = pci_docfgregread(bus, slot, func, PCIR_INTLINE, 1); 227 return (pci_i386_map_intline(line)); 228 } 229 return (pci_docfgregread(bus, slot, func, reg, bytes)); 230} 231 232/* 233 * Write configuration space register 234 */ 235void 236pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) 237{ 238 239 if (cfgmech == CFGMECH_PCIE && 240 (bus >= pcie_minbus && bus <= pcie_maxbus) && 241 (bus != 0 || !(1 << slot & pcie_badslots))) 242 pciereg_cfgwrite(bus, slot, func, reg, data, bytes); 243 else 244 pcireg_cfgwrite(bus, slot, func, reg, data, bytes); 245} 246 247/* 248 * Configuration space access using direct register operations 249 */ 250 251/* enable configuration space accesses and return data port address */ 252static int 253pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes) 254{ 255 int dataport = 0; 256 257#ifdef XBOX 258 if (arch_i386_is_xbox) { 259 /* 260 * The Xbox MCPX chipset is a derivative of the nForce 1 261 * chipset. It almost has the same bus layout; some devices 262 * cannot be used, because they have been removed. 263 */ 264 265 /* 266 * Devices 00:00.1 and 00:00.2 used to be memory controllers on 267 * the nForce chipset, but on the Xbox, using them will lockup 268 * the chipset. 269 */ 270 if (bus == 0 && slot == 0 && (func == 1 || func == 2)) 271 return dataport; 272 273 /* 274 * Bus 1 only contains a VGA controller at 01:00.0. When you try 275 * to probe beyond that device, you only get garbage, which 276 * could cause lockups. 277 */ 278 if (bus == 1 && (slot != 0 || func != 0)) 279 return dataport; 280 281 /* 282 * Bus 2 used to contain the AGP controller, but the Xbox MCPX 283 * doesn't have one. Probing it can cause lockups. 284 */ 285 if (bus >= 2) 286 return dataport; 287 } 288#endif 289 290 if (bus <= PCI_BUSMAX 291 && slot < devmax 292 && func <= PCI_FUNCMAX 293 && (unsigned)reg <= PCI_REGMAX 294 && bytes != 3 295 && (unsigned)bytes <= 4 296 && (reg & (bytes - 1)) == 0) { 297 switch (cfgmech) { 298 case CFGMECH_PCIE: 299 case CFGMECH_1: 300 outl(CONF1_ADDR_PORT, (1U << 31) 301 | (bus << 16) | (slot << 11) 302 | (func << 8) | (reg & ~0x03)); 303 dataport = CONF1_DATA_PORT + (reg & 0x03); 304 break; 305 case CFGMECH_2: 306 outb(CONF2_ENABLE_PORT, 0xf0 | (func << 1)); 307 outb(CONF2_FORWARD_PORT, bus); 308 dataport = 0xc000 | (slot << 8) | reg; 309 break; 310 } 311 } 312 return (dataport); 313} 314 315/* disable configuration space accesses */ 316static void 317pci_cfgdisable(void) 318{ 319 switch (cfgmech) { 320 case CFGMECH_PCIE: 321 case CFGMECH_1: 322 /* 323 * Do nothing for the config mechanism 1 case. 324 * Writing a 0 to the address port can apparently 325 * confuse some bridges and cause spurious 326 * access failures. 327 */ 328 break; 329 case CFGMECH_2: 330 outb(CONF2_ENABLE_PORT, 0); 331 break; 332 } 333} 334 335static int 336pcireg_cfgread(int bus, int slot, int func, int reg, int bytes) 337{ 338 int data = -1; 339 int port; 340 341 mtx_lock_spin(&pcicfg_mtx); 342 port = pci_cfgenable(bus, slot, func, reg, bytes); 343 if (port != 0) { 344 switch (bytes) { 345 case 1: 346 data = inb(port); 347 break; 348 case 2: 349 data = inw(port); 350 break; 351 case 4: 352 data = inl(port); 353 break; 354 } 355 pci_cfgdisable(); 356 } 357 mtx_unlock_spin(&pcicfg_mtx); 358 return (data); 359} 360 361static void 362pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes) 363{ 364 int port; 365 366 mtx_lock_spin(&pcicfg_mtx); 367 port = pci_cfgenable(bus, slot, func, reg, bytes); 368 if (port != 0) { 369 switch (bytes) { 370 case 1: 371 outb(port, data); 372 break; 373 case 2: 374 outw(port, data); 375 break; 376 case 4: 377 outl(port, data); 378 break; 379 } 380 pci_cfgdisable(); 381 } 382 mtx_unlock_spin(&pcicfg_mtx); 383} 384
|
393#ifndef XEN
|
385/* check whether the configuration mechanism has been correctly identified */ 386static int 387pci_cfgcheck(int maxdev) 388{ 389 uint32_t id, class; 390 uint8_t header; 391 uint8_t device; 392 int port; 393 394 if (bootverbose) 395 printf("pci_cfgcheck:\tdevice "); 396 397 for (device = 0; device < maxdev; device++) { 398 if (bootverbose) 399 printf("%d ", device); 400 401 port = pci_cfgenable(0, device, 0, 0, 4); 402 id = inl(port); 403 if (id == 0 || id == 0xffffffff) 404 continue; 405 406 port = pci_cfgenable(0, device, 0, 8, 4); 407 class = inl(port) >> 8; 408 if (bootverbose) 409 printf("[class=%06x] ", class); 410 if (class == 0 || (class & 0xf870ff) != 0) 411 continue; 412 413 port = pci_cfgenable(0, device, 0, 14, 1); 414 header = inb(port); 415 if (bootverbose) 416 printf("[hdr=%02x] ", header); 417 if ((header & 0x7e) != 0) 418 continue; 419 420 if (bootverbose) 421 printf("is there (id=%08x)\n", id); 422 423 pci_cfgdisable(); 424 return (1); 425 } 426 if (bootverbose) 427 printf("-- nothing found\n"); 428 429 pci_cfgdisable(); 430 return (0); 431} 432 433static int 434pcireg_cfgopen(void) 435{ 436 uint32_t mode1res, oldval1; 437 uint8_t mode2res, oldval2; 438 439 /* Check for type #1 first. */ 440 oldval1 = inl(CONF1_ADDR_PORT); 441 442 if (bootverbose) { 443 printf("pci_open(1):\tmode 1 addr port (0x0cf8) is 0x%08x\n", 444 oldval1); 445 } 446 447 cfgmech = CFGMECH_1; 448 devmax = 32; 449 450 outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK); 451 DELAY(1); 452 mode1res = inl(CONF1_ADDR_PORT); 453 outl(CONF1_ADDR_PORT, oldval1); 454 455 if (bootverbose) 456 printf("pci_open(1a):\tmode1res=0x%08x (0x%08lx)\n", mode1res, 457 CONF1_ENABLE_CHK); 458 459 if (mode1res) { 460 if (pci_cfgcheck(32)) 461 return (cfgmech); 462 } 463 464 outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK1); 465 mode1res = inl(CONF1_ADDR_PORT); 466 outl(CONF1_ADDR_PORT, oldval1); 467 468 if (bootverbose) 469 printf("pci_open(1b):\tmode1res=0x%08x (0x%08lx)\n", mode1res, 470 CONF1_ENABLE_CHK1); 471 472 if ((mode1res & CONF1_ENABLE_MSK1) == CONF1_ENABLE_RES1) { 473 if (pci_cfgcheck(32)) 474 return (cfgmech); 475 } 476 477 /* Type #1 didn't work, so try type #2. */ 478 oldval2 = inb(CONF2_ENABLE_PORT); 479 480 if (bootverbose) { 481 printf("pci_open(2):\tmode 2 enable port (0x0cf8) is 0x%02x\n", 482 oldval2); 483 } 484 485 if ((oldval2 & 0xf0) == 0) { 486 487 cfgmech = CFGMECH_2; 488 devmax = 16; 489 490 outb(CONF2_ENABLE_PORT, CONF2_ENABLE_CHK); 491 mode2res = inb(CONF2_ENABLE_PORT); 492 outb(CONF2_ENABLE_PORT, oldval2); 493 494 if (bootverbose) 495 printf("pci_open(2a):\tmode2res=0x%02x (0x%02x)\n", 496 mode2res, CONF2_ENABLE_CHK); 497 498 if (mode2res == CONF2_ENABLE_RES) { 499 if (bootverbose) 500 printf("pci_open(2a):\tnow trying mechanism 2\n"); 501 502 if (pci_cfgcheck(16)) 503 return (cfgmech); 504 } 505 } 506 507 /* Nothing worked, so punt. */ 508 cfgmech = CFGMECH_NONE; 509 devmax = 0; 510 return (cfgmech); 511} 512 513int 514pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus) 515{ 516 struct pcie_cfg_list *pcielist; 517 struct pcie_cfg_elem *pcie_array, *elem; 518#ifdef SMP 519 struct pcpu *pc; 520#endif 521 vm_offset_t va; 522 uint32_t val1, val2; 523 int i, slot; 524 525 if (!mcfg_enable) 526 return (0); 527 528 if (minbus != 0) 529 return (0); 530 531#ifndef PAE 532 if (base >= 0x100000000) { 533 if (bootverbose) 534 printf( 535 "PCI: Memory Mapped PCI configuration area base 0x%jx too high\n", 536 (uintmax_t)base); 537 return (0); 538 } 539#endif 540 541 if (bootverbose) 542 printf("PCIe: Memory Mapped configuration base @ 0x%jx\n", 543 (uintmax_t)base); 544 545#ifdef SMP 546 STAILQ_FOREACH(pc, &cpuhead, pc_allcpu) 547#endif 548 { 549 550 pcie_array = malloc(sizeof(struct pcie_cfg_elem) * PCIE_CACHE, 551 M_DEVBUF, M_NOWAIT); 552 if (pcie_array == NULL) 553 return (0); 554 555 va = kva_alloc(PCIE_CACHE * PAGE_SIZE); 556 if (va == 0) { 557 free(pcie_array, M_DEVBUF); 558 return (0); 559 } 560 561#ifdef SMP 562 pcielist = &pcie_list[pc->pc_cpuid]; 563#else 564 pcielist = &pcie_list[0]; 565#endif 566 TAILQ_INIT(pcielist); 567 for (i = 0; i < PCIE_CACHE; i++) { 568 elem = &pcie_array[i]; 569 elem->vapage = va + (i * PAGE_SIZE); 570 elem->papage = 0; 571 TAILQ_INSERT_HEAD(pcielist, elem, elem); 572 } 573 } 574 575 pcie_base = base; 576 pcie_minbus = minbus; 577 pcie_maxbus = maxbus; 578 cfgmech = CFGMECH_PCIE; 579 devmax = 32; 580 581 /* 582 * On some AMD systems, some of the devices on bus 0 are 583 * inaccessible using memory-mapped PCI config access. Walk 584 * bus 0 looking for such devices. For these devices, we will 585 * fall back to using type 1 config access instead. 586 */ 587 if (pci_cfgregopen() != 0) { 588 for (slot = 0; slot <= PCI_SLOTMAX; slot++) { 589 val1 = pcireg_cfgread(0, slot, 0, 0, 4); 590 if (val1 == 0xffffffff) 591 continue; 592 593 val2 = pciereg_cfgread(0, slot, 0, 0, 4); 594 if (val2 != val1) 595 pcie_badslots |= (1 << slot); 596 } 597 } 598 599 return (1); 600}
|
610#endif /* !XEN */
|
601 602#define PCIE_PADDR(base, reg, bus, slot, func) \ 603 ((base) + \ 604 ((((bus) & 0xff) << 20) | \ 605 (((slot) & 0x1f) << 15) | \ 606 (((func) & 0x7) << 12) | \ 607 ((reg) & 0xfff))) 608 609static __inline vm_offset_t 610pciereg_findaddr(int bus, unsigned slot, unsigned func, unsigned reg) 611{ 612 struct pcie_cfg_list *pcielist; 613 struct pcie_cfg_elem *elem; 614 vm_paddr_t pa, papage; 615 616 pa = PCIE_PADDR(pcie_base, reg, bus, slot, func); 617 papage = pa & ~PAGE_MASK; 618 619 /* 620 * Find an element in the cache that matches the physical page desired, 621 * or create a new mapping from the least recently used element. 622 * A very simple LRU algorithm is used here, does it need to be more 623 * efficient? 624 */ 625 pcielist = &pcie_list[PCPU_GET(cpuid)]; 626 TAILQ_FOREACH(elem, pcielist, elem) { 627 if (elem->papage == papage) 628 break; 629 } 630 631 if (elem == NULL) { 632 elem = TAILQ_LAST(pcielist, pcie_cfg_list); 633 if (elem->papage != 0) { 634 pmap_kremove(elem->vapage); 635 invlpg(elem->vapage); 636 } 637 pmap_kenter(elem->vapage, papage); 638 elem->papage = papage; 639 } 640 641 if (elem != TAILQ_FIRST(pcielist)) { 642 TAILQ_REMOVE(pcielist, elem, elem); 643 TAILQ_INSERT_HEAD(pcielist, elem, elem); 644 } 645 return (elem->vapage | (pa & PAGE_MASK)); 646} 647 648/* 649 * AMD BIOS And Kernel Developer's Guides for CPU families starting with 10h 650 * have a requirement that all accesses to the memory mapped PCI configuration 651 * space are done using AX class of registers. 652 * Since other vendors do not currently have any contradicting requirements 653 * the AMD access pattern is applied universally. 654 */ 655 656static int 657pciereg_cfgread(int bus, unsigned slot, unsigned func, unsigned reg, 658 unsigned bytes) 659{ 660 vm_offset_t va; 661 int data = -1; 662 663 if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || 664 func > PCI_FUNCMAX || reg > PCIE_REGMAX) 665 return (-1); 666 667 critical_enter(); 668 va = pciereg_findaddr(bus, slot, func, reg); 669 670 switch (bytes) { 671 case 4: 672 __asm("movl %1, %0" : "=a" (data) 673 : "m" (*(volatile uint32_t *)va)); 674 break; 675 case 2: 676 __asm("movzwl %1, %0" : "=a" (data) 677 : "m" (*(volatile uint16_t *)va)); 678 break; 679 case 1: 680 __asm("movzbl %1, %0" : "=a" (data) 681 : "m" (*(volatile uint8_t *)va)); 682 break; 683 } 684 685 critical_exit(); 686 return (data); 687} 688 689static void 690pciereg_cfgwrite(int bus, unsigned slot, unsigned func, unsigned reg, int data, 691 unsigned bytes) 692{ 693 vm_offset_t va; 694 695 if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || 696 func > PCI_FUNCMAX || reg > PCIE_REGMAX) 697 return; 698 699 critical_enter(); 700 va = pciereg_findaddr(bus, slot, func, reg); 701 702 switch (bytes) { 703 case 4: 704 __asm("movl %1, %0" : "=m" (*(volatile uint32_t *)va) 705 : "a" (data)); 706 break; 707 case 2: 708 __asm("movw %1, %0" : "=m" (*(volatile uint16_t *)va) 709 : "a" ((uint16_t)data)); 710 break; 711 case 1: 712 __asm("movb %1, %0" : "=m" (*(volatile uint8_t *)va) 713 : "a" ((uint8_t)data)); 714 break; 715 } 716 717 critical_exit(); 718}
|