aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/iseries/pci.c
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2006-11-11 01:25:10 -0500
committerPaul Mackerras <paulus@samba.org>2006-12-04 04:38:52 -0500
commit4cb3cee03d558fd457cb58f56c80a2a09a66110c (patch)
treefe903107d098871a7babc1e3432448758c542cde /arch/powerpc/platforms/iseries/pci.c
parentd03f387eb321189bc2ba278b6ca82f1a45cf19d6 (diff)
[POWERPC] Allow hooking of PCI MMIO & PIO accessors on 64 bits
This patch reworks the way iSeries hooks on PCI IO operations (both MMIO and PIO) and provides a generic way for other platforms to do so (we have need to do that for various other platforms). While reworking the IO ops, I ended up doing some spring cleaning in io.h and eeh.h which I might want to split into 2 or 3 patches (among others, eeh.h had a lot of useless stuff in it). A side effect is that EEH for PIO should work now (it used to pass IO ports down to the eeh address check functions which is bogus). Also, new are MMIO "repeat" ops, which other archs like ARM already had, and that we have too now: readsb, readsw, readsl, writesb, writesw, writesl. In the long run, I might also make EEH use the hooks instead of wrapping at the toplevel, which would make things even cleaner and relegate EEH completely in platforms/iseries, but we have to measure the performance impact there (though it's really only on MMIO reads) Since I also need to hook on ioremap, I shuffled the functions a bit there. I introduced ioremap_flags() to use by drivers who want to pass explicit flags to ioremap (and it can be hooked). The old __ioremap() is still there as a low level and cannot be hooked, thus drivers who use it should migrate unless they know they want the low level version. The patch "arch provides generic iomap missing accessors" (should be number 4 in this series) is a pre-requisite to provide full iomap API support with this patch. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/platforms/iseries/pci.c')
-rw-r--r--arch/powerpc/platforms/iseries/pci.c370
1 files changed, 155 insertions, 215 deletions
diff --git a/arch/powerpc/platforms/iseries/pci.c b/arch/powerpc/platforms/iseries/pci.c
index a90ae42a7bc2..4a06d9c34986 100644
--- a/arch/powerpc/platforms/iseries/pci.c
+++ b/arch/powerpc/platforms/iseries/pci.c
@@ -156,53 +156,6 @@ static void pci_Log_Error(char *Error_Text, int Bus, int SubBus,
156} 156}
157 157
158/* 158/*
159 * iSeries_pcibios_init
160 *
161 * Description:
162 * This function checks for all possible system PCI host bridges that connect
163 * PCI buses. The system hypervisor is queried as to the guest partition
164 * ownership status. A pci_controller is built for any bus which is partially
165 * owned or fully owned by this guest partition.
166 */
167void iSeries_pcibios_init(void)
168{
169 struct pci_controller *phb;
170 struct device_node *root = of_find_node_by_path("/");
171 struct device_node *node = NULL;
172
173 if (root == NULL) {
174 printk(KERN_CRIT "iSeries_pcibios_init: can't find root "
175 "of device tree\n");
176 return;
177 }
178 while ((node = of_get_next_child(root, node)) != NULL) {
179 HvBusNumber bus;
180 const u32 *busp;
181
182 if ((node->type == NULL) || (strcmp(node->type, "pci") != 0))
183 continue;
184
185 busp = get_property(node, "bus-range", NULL);
186 if (busp == NULL)
187 continue;
188 bus = *busp;
189 printk("bus %d appears to exist\n", bus);
190 phb = pcibios_alloc_controller(node);
191 if (phb == NULL)
192 continue;
193
194 phb->pci_mem_offset = phb->local_number = bus;
195 phb->first_busno = bus;
196 phb->last_busno = bus;
197 phb->ops = &iSeries_pci_ops;
198 }
199
200 of_node_put(root);
201
202 pci_devs_phb_init();
203}
204
205/*
206 * iSeries_pci_final_fixup(void) 159 * iSeries_pci_final_fixup(void)
207 */ 160 */
208void __init iSeries_pci_final_fixup(void) 161void __init iSeries_pci_final_fixup(void)
@@ -438,11 +391,7 @@ static inline struct device_node *xlate_iomm_address(
438/* 391/*
439 * Read MM I/O Instructions for the iSeries 392 * Read MM I/O Instructions for the iSeries
440 * On MM I/O error, all ones are returned and iSeries_pci_IoError is cal 393 * On MM I/O error, all ones are returned and iSeries_pci_IoError is cal
441 * else, data is returned in big Endian format. 394 * else, data is returned in Big Endian format.
442 *
443 * iSeries_Read_Byte = Read Byte ( 8 bit)
444 * iSeries_Read_Word = Read Word (16 bit)
445 * iSeries_Read_Long = Read Long (32 bit)
446 */ 395 */
447static u8 iSeries_Read_Byte(const volatile void __iomem *IoAddress) 396static u8 iSeries_Read_Byte(const volatile void __iomem *IoAddress)
448{ 397{
@@ -462,14 +411,15 @@ static u8 iSeries_Read_Byte(const volatile void __iomem *IoAddress)
462 num_printed = 0; 411 num_printed = 0;
463 } 412 }
464 if (num_printed++ < 10) 413 if (num_printed++ < 10)
465 printk(KERN_ERR "iSeries_Read_Byte: invalid access at IO address %p\n", IoAddress); 414 printk(KERN_ERR "iSeries_Read_Byte: invalid access at IO address %p\n",
415 IoAddress);
466 return 0xff; 416 return 0xff;
467 } 417 }
468 do { 418 do {
469 HvCall3Ret16(HvCallPciBarLoad8, &ret, dsa, BarOffset, 0); 419 HvCall3Ret16(HvCallPciBarLoad8, &ret, dsa, BarOffset, 0);
470 } while (CheckReturnCode("RDB", DevNode, &retry, ret.rc) != 0); 420 } while (CheckReturnCode("RDB", DevNode, &retry, ret.rc) != 0);
471 421
472 return (u8)ret.value; 422 return ret.value;
473} 423}
474 424
475static u16 iSeries_Read_Word(const volatile void __iomem *IoAddress) 425static u16 iSeries_Read_Word(const volatile void __iomem *IoAddress)
@@ -490,7 +440,8 @@ static u16 iSeries_Read_Word(const volatile void __iomem *IoAddress)
490 num_printed = 0; 440 num_printed = 0;
491 } 441 }
492 if (num_printed++ < 10) 442 if (num_printed++ < 10)
493 printk(KERN_ERR "iSeries_Read_Word: invalid access at IO address %p\n", IoAddress); 443 printk(KERN_ERR "iSeries_Read_Word: invalid access at IO address %p\n",
444 IoAddress);
494 return 0xffff; 445 return 0xffff;
495 } 446 }
496 do { 447 do {
@@ -498,7 +449,7 @@ static u16 iSeries_Read_Word(const volatile void __iomem *IoAddress)
498 BarOffset, 0); 449 BarOffset, 0);
499 } while (CheckReturnCode("RDW", DevNode, &retry, ret.rc) != 0); 450 } while (CheckReturnCode("RDW", DevNode, &retry, ret.rc) != 0);
500 451
501 return swab16((u16)ret.value); 452 return ret.value;
502} 453}
503 454
504static u32 iSeries_Read_Long(const volatile void __iomem *IoAddress) 455static u32 iSeries_Read_Long(const volatile void __iomem *IoAddress)
@@ -519,7 +470,8 @@ static u32 iSeries_Read_Long(const volatile void __iomem *IoAddress)
519 num_printed = 0; 470 num_printed = 0;
520 } 471 }
521 if (num_printed++ < 10) 472 if (num_printed++ < 10)
522 printk(KERN_ERR "iSeries_Read_Long: invalid access at IO address %p\n", IoAddress); 473 printk(KERN_ERR "iSeries_Read_Long: invalid access at IO address %p\n",
474 IoAddress);
523 return 0xffffffff; 475 return 0xffffffff;
524 } 476 }
525 do { 477 do {
@@ -527,15 +479,12 @@ static u32 iSeries_Read_Long(const volatile void __iomem *IoAddress)
527 BarOffset, 0); 479 BarOffset, 0);
528 } while (CheckReturnCode("RDL", DevNode, &retry, ret.rc) != 0); 480 } while (CheckReturnCode("RDL", DevNode, &retry, ret.rc) != 0);
529 481
530 return swab32((u32)ret.value); 482 return ret.value;
531} 483}
532 484
533/* 485/*
534 * Write MM I/O Instructions for the iSeries 486 * Write MM I/O Instructions for the iSeries
535 * 487 *
536 * iSeries_Write_Byte = Write Byte (8 bit)
537 * iSeries_Write_Word = Write Word(16 bit)
538 * iSeries_Write_Long = Write Long(32 bit)
539 */ 488 */
540static void iSeries_Write_Byte(u8 data, volatile void __iomem *IoAddress) 489static void iSeries_Write_Byte(u8 data, volatile void __iomem *IoAddress)
541{ 490{
@@ -581,11 +530,12 @@ static void iSeries_Write_Word(u16 data, volatile void __iomem *IoAddress)
581 num_printed = 0; 530 num_printed = 0;
582 } 531 }
583 if (num_printed++ < 10) 532 if (num_printed++ < 10)
584 printk(KERN_ERR "iSeries_Write_Word: invalid access at IO address %p\n", IoAddress); 533 printk(KERN_ERR "iSeries_Write_Word: invalid access at IO address %p\n",
534 IoAddress);
585 return; 535 return;
586 } 536 }
587 do { 537 do {
588 rc = HvCall4(HvCallPciBarStore16, dsa, BarOffset, swab16(data), 0); 538 rc = HvCall4(HvCallPciBarStore16, dsa, BarOffset, data, 0);
589 } while (CheckReturnCode("WWW", DevNode, &retry, rc) != 0); 539 } while (CheckReturnCode("WWW", DevNode, &retry, rc) != 0);
590} 540}
591 541
@@ -607,231 +557,221 @@ static void iSeries_Write_Long(u32 data, volatile void __iomem *IoAddress)
607 num_printed = 0; 557 num_printed = 0;
608 } 558 }
609 if (num_printed++ < 10) 559 if (num_printed++ < 10)
610 printk(KERN_ERR "iSeries_Write_Long: invalid access at IO address %p\n", IoAddress); 560 printk(KERN_ERR "iSeries_Write_Long: invalid access at IO address %p\n",
561 IoAddress);
611 return; 562 return;
612 } 563 }
613 do { 564 do {
614 rc = HvCall4(HvCallPciBarStore32, dsa, BarOffset, swab32(data), 0); 565 rc = HvCall4(HvCallPciBarStore32, dsa, BarOffset, data, 0);
615 } while (CheckReturnCode("WWL", DevNode, &retry, rc) != 0); 566 } while (CheckReturnCode("WWL", DevNode, &retry, rc) != 0);
616} 567}
617 568
618extern unsigned char __raw_readb(const volatile void __iomem *addr) 569static u8 iseries_readb(const volatile void __iomem *addr)
619{ 570{
620 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 571 return iSeries_Read_Byte(addr);
621
622 return *(volatile unsigned char __force *)addr;
623} 572}
624EXPORT_SYMBOL(__raw_readb);
625 573
626extern unsigned short __raw_readw(const volatile void __iomem *addr) 574static u16 iseries_readw(const volatile void __iomem *addr)
627{ 575{
628 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 576 return le16_to_cpu(iSeries_Read_Word(addr));
629
630 return *(volatile unsigned short __force *)addr;
631} 577}
632EXPORT_SYMBOL(__raw_readw);
633 578
634extern unsigned int __raw_readl(const volatile void __iomem *addr) 579static u32 iseries_readl(const volatile void __iomem *addr)
635{ 580{
636 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 581 return le32_to_cpu(iSeries_Read_Long(addr));
637
638 return *(volatile unsigned int __force *)addr;
639} 582}
640EXPORT_SYMBOL(__raw_readl);
641 583
642extern unsigned long __raw_readq(const volatile void __iomem *addr) 584static u16 iseries_readw_be(const volatile void __iomem *addr)
643{ 585{
644 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 586 return iSeries_Read_Word(addr);
645
646 return *(volatile unsigned long __force *)addr;
647} 587}
648EXPORT_SYMBOL(__raw_readq);
649 588
650extern void __raw_writeb(unsigned char v, volatile void __iomem *addr) 589static u32 iseries_readl_be(const volatile void __iomem *addr)
651{ 590{
652 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 591 return iSeries_Read_Long(addr);
653
654 *(volatile unsigned char __force *)addr = v;
655} 592}
656EXPORT_SYMBOL(__raw_writeb);
657 593
658extern void __raw_writew(unsigned short v, volatile void __iomem *addr) 594static void iseries_writeb(u8 data, volatile void __iomem *addr)
659{ 595{
660 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 596 iSeries_Write_Byte(data, addr);
661
662 *(volatile unsigned short __force *)addr = v;
663} 597}
664EXPORT_SYMBOL(__raw_writew);
665 598
666extern void __raw_writel(unsigned int v, volatile void __iomem *addr) 599static void iseries_writew(u16 data, volatile void __iomem *addr)
667{ 600{
668 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 601 iSeries_Write_Word(cpu_to_le16(data), addr);
669
670 *(volatile unsigned int __force *)addr = v;
671} 602}
672EXPORT_SYMBOL(__raw_writel);
673 603
674extern void __raw_writeq(unsigned long v, volatile void __iomem *addr) 604static void iseries_writel(u32 data, volatile void __iomem *addr)
675{ 605{
676 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 606 iSeries_Write_Long(cpu_to_le32(data), addr);
677
678 *(volatile unsigned long __force *)addr = v;
679} 607}
680EXPORT_SYMBOL(__raw_writeq);
681 608
682int in_8(const volatile unsigned char __iomem *addr) 609static void iseries_writew_be(u16 data, volatile void __iomem *addr)
683{ 610{
684 if (firmware_has_feature(FW_FEATURE_ISERIES)) 611 iSeries_Write_Word(data, addr);
685 return iSeries_Read_Byte(addr);
686 return __in_8(addr);
687} 612}
688EXPORT_SYMBOL(in_8);
689 613
690void out_8(volatile unsigned char __iomem *addr, int val) 614static void iseries_writel_be(u32 data, volatile void __iomem *addr)
691{ 615{
692 if (firmware_has_feature(FW_FEATURE_ISERIES)) 616 iSeries_Write_Long(data, addr);
693 iSeries_Write_Byte(val, addr);
694 else
695 __out_8(addr, val);
696} 617}
697EXPORT_SYMBOL(out_8);
698 618
699int in_le16(const volatile unsigned short __iomem *addr) 619static void iseries_readsb(const volatile void __iomem *addr, void *buf,
620 unsigned long count)
700{ 621{
701 if (firmware_has_feature(FW_FEATURE_ISERIES)) 622 u8 *dst = buf;
702 return iSeries_Read_Word(addr); 623 while(count-- > 0)
703 return __in_le16(addr); 624 *(dst++) = iSeries_Read_Byte(addr);
704} 625}
705EXPORT_SYMBOL(in_le16);
706 626
707int in_be16(const volatile unsigned short __iomem *addr) 627static void iseries_readsw(const volatile void __iomem *addr, void *buf,
628 unsigned long count)
708{ 629{
709 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 630 u16 *dst = buf;
710 631 while(count-- > 0)
711 return __in_be16(addr); 632 *(dst++) = iSeries_Read_Word(addr);
712} 633}
713EXPORT_SYMBOL(in_be16);
714 634
715void out_le16(volatile unsigned short __iomem *addr, int val) 635static void iseries_readsl(const volatile void __iomem *addr, void *buf,
636 unsigned long count)
716{ 637{
717 if (firmware_has_feature(FW_FEATURE_ISERIES)) 638 u32 *dst = buf;
718 iSeries_Write_Word(val, addr); 639 while(count-- > 0)
719 else 640 *(dst++) = iSeries_Read_Long(addr);
720 __out_le16(addr, val);
721} 641}
722EXPORT_SYMBOL(out_le16);
723 642
724void out_be16(volatile unsigned short __iomem *addr, int val) 643static void iseries_writesb(volatile void __iomem *addr, const void *buf,
644 unsigned long count)
725{ 645{
726 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 646 const u8 *src = buf;
727 647 while(count-- > 0)
728 __out_be16(addr, val); 648 iSeries_Write_Byte(*(src++), addr);
729} 649}
730EXPORT_SYMBOL(out_be16);
731 650
732unsigned in_le32(const volatile unsigned __iomem *addr) 651static void iseries_writesw(volatile void __iomem *addr, const void *buf,
652 unsigned long count)
733{ 653{
734 if (firmware_has_feature(FW_FEATURE_ISERIES)) 654 const u16 *src = buf;
735 return iSeries_Read_Long(addr); 655 while(count-- > 0)
736 return __in_le32(addr); 656 iSeries_Write_Word(*(src++), addr);
737} 657}
738EXPORT_SYMBOL(in_le32);
739 658
740unsigned in_be32(const volatile unsigned __iomem *addr) 659static void iseries_writesl(volatile void __iomem *addr, const void *buf,
660 unsigned long count)
741{ 661{
742 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 662 const u32 *src = buf;
743 663 while(count-- > 0)
744 return __in_be32(addr); 664 iSeries_Write_Long(*(src++), addr);
745} 665}
746EXPORT_SYMBOL(in_be32);
747 666
748void out_le32(volatile unsigned __iomem *addr, int val) 667static void iseries_memset_io(volatile void __iomem *addr, int c,
668 unsigned long n)
749{ 669{
750 if (firmware_has_feature(FW_FEATURE_ISERIES)) 670 volatile char __iomem *d = addr;
751 iSeries_Write_Long(val, addr);
752 else
753 __out_le32(addr, val);
754}
755EXPORT_SYMBOL(out_le32);
756 671
757void out_be32(volatile unsigned __iomem *addr, int val) 672 while (n-- > 0)
758{ 673 iSeries_Write_Byte(c, d++);
759 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES));
760
761 __out_be32(addr, val);
762} 674}
763EXPORT_SYMBOL(out_be32);
764 675
765unsigned long in_le64(const volatile unsigned long __iomem *addr) 676static void iseries_memcpy_fromio(void *dest, const volatile void __iomem *src,
677 unsigned long n)
766{ 678{
767 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 679 char *d = dest;
680 const volatile char __iomem *s = src;
768 681
769 return __in_le64(addr); 682 while (n-- > 0)
683 *d++ = iSeries_Read_Byte(s++);
770} 684}
771EXPORT_SYMBOL(in_le64);
772 685
773unsigned long in_be64(const volatile unsigned long __iomem *addr) 686static void iseries_memcpy_toio(volatile void __iomem *dest, const void *src,
687 unsigned long n)
774{ 688{
775 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 689 const char *s = src;
690 volatile char __iomem *d = dest;
776 691
777 return __in_be64(addr); 692 while (n-- > 0)
693 iSeries_Write_Byte(*s++, d++);
778} 694}
779EXPORT_SYMBOL(in_be64);
780
781void out_le64(volatile unsigned long __iomem *addr, unsigned long val)
782{
783 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES));
784 695
785 __out_le64(addr, val); 696/* We only set MMIO ops. The default PIO ops will be default
786} 697 * to the MMIO ops + pci_io_base which is 0 on iSeries as
787EXPORT_SYMBOL(out_le64); 698 * expected so both should work.
699 *
700 * Note that we don't implement the readq/writeq versions as
701 * I don't know of an HV call for doing so. Thus, the default
702 * operation will be used instead, which will fault a the value
703 * return by iSeries for MMIO addresses always hits a non mapped
704 * area. This is as good as the BUG() we used to have there.
705 */
706static struct ppc_pci_io __initdata iseries_pci_io = {
707 .readb = iseries_readb,
708 .readw = iseries_readw,
709 .readl = iseries_readl,
710 .readw_be = iseries_readw_be,
711 .readl_be = iseries_readl_be,
712 .writeb = iseries_writeb,
713 .writew = iseries_writew,
714 .writel = iseries_writel,
715 .writew_be = iseries_writew_be,
716 .writel_be = iseries_writel_be,
717 .readsb = iseries_readsb,
718 .readsw = iseries_readsw,
719 .readsl = iseries_readsl,
720 .writesb = iseries_writesb,
721 .writesw = iseries_writesw,
722 .writesl = iseries_writesl,
723 .memset_io = iseries_memset_io,
724 .memcpy_fromio = iseries_memcpy_fromio,
725 .memcpy_toio = iseries_memcpy_toio,
726};
788 727
789void out_be64(volatile unsigned long __iomem *addr, unsigned long val) 728/*
729 * iSeries_pcibios_init
730 *
731 * Description:
732 * This function checks for all possible system PCI host bridges that connect
733 * PCI buses. The system hypervisor is queried as to the guest partition
734 * ownership status. A pci_controller is built for any bus which is partially
735 * owned or fully owned by this guest partition.
736 */
737void __init iSeries_pcibios_init(void)
790{ 738{
791 BUG_ON(firmware_has_feature(FW_FEATURE_ISERIES)); 739 struct pci_controller *phb;
740 struct device_node *root = of_find_node_by_path("/");
741 struct device_node *node = NULL;
792 742
793 __out_be64(addr, val); 743 /* Install IO hooks */
794} 744 ppc_pci_io = iseries_pci_io;
795EXPORT_SYMBOL(out_be64);
796 745
797void memset_io(volatile void __iomem *addr, int c, unsigned long n) 746 if (root == NULL) {
798{ 747 printk(KERN_CRIT "iSeries_pcibios_init: can't find root "
799 if (firmware_has_feature(FW_FEATURE_ISERIES)) { 748 "of device tree\n");
800 volatile char __iomem *d = addr; 749 return;
750 }
751 while ((node = of_get_next_child(root, node)) != NULL) {
752 HvBusNumber bus;
753 const u32 *busp;
801 754
802 while (n-- > 0) { 755 if ((node->type == NULL) || (strcmp(node->type, "pci") != 0))
803 iSeries_Write_Byte(c, d++); 756 continue;
804 }
805 } else
806 eeh_memset_io(addr, c, n);
807}
808EXPORT_SYMBOL(memset_io);
809 757
810void memcpy_fromio(void *dest, const volatile void __iomem *src, 758 busp = get_property(node, "bus-range", NULL);
811 unsigned long n) 759 if (busp == NULL)
812{ 760 continue;
813 if (firmware_has_feature(FW_FEATURE_ISERIES)) { 761 bus = *busp;
814 char *d = dest; 762 printk("bus %d appears to exist\n", bus);
815 const volatile char __iomem *s = src; 763 phb = pcibios_alloc_controller(node);
764 if (phb == NULL)
765 continue;
816 766
817 while (n-- > 0) { 767 phb->pci_mem_offset = phb->local_number = bus;
818 *d++ = iSeries_Read_Byte(s++); 768 phb->first_busno = bus;
819 } 769 phb->last_busno = bus;
820 } else 770 phb->ops = &iSeries_pci_ops;
821 eeh_memcpy_fromio(dest, src, n); 771 }
822}
823EXPORT_SYMBOL(memcpy_fromio);
824 772
825void memcpy_toio(volatile void __iomem *dest, const void *src, unsigned long n) 773 of_node_put(root);
826{
827 if (firmware_has_feature(FW_FEATURE_ISERIES)) {
828 const char *s = src;
829 volatile char __iomem *d = dest;
830 774
831 while (n-- > 0) { 775 pci_devs_phb_init();
832 iSeries_Write_Byte(*s++, d++);
833 }
834 } else
835 eeh_memcpy_toio(dest, src, n);
836} 776}
837EXPORT_SYMBOL(memcpy_toio); 777