| /* | |
| * This file is subject to the terms and conditions of the GNU General Public | |
| * License. See the file "COPYING" in the main directory of this archive | |
| * for more details. | |
| * | |
| * Copyright (c) 2000-2007 Silicon Graphics, Inc. All Rights Reserved. | |
| */ | |
| #include <linux/module.h> | |
| #include <asm/sn/nodepda.h> | |
| #include <asm/sn/addrs.h> | |
| #include <asm/sn/arch.h> | |
| #include <asm/sn/sn_cpuid.h> | |
| #include <asm/sn/pda.h> | |
| #include <asm/sn/shubio.h> | |
| #include <asm/nodedata.h> | |
| #include <asm/delay.h> | |
| #include <linux/bootmem.h> | |
| #include <linux/string.h> | |
| #include <linux/sched.h> | |
| #include <linux/slab.h> | |
| #include <asm/sn/bte.h> | |
| #ifndef L1_CACHE_MASK | |
| #define L1_CACHE_MASK (L1_CACHE_BYTES - 1) | |
| #endif | |
| /* two interfaces on two btes */ | |
| #define MAX_INTERFACES_TO_TRY 4 | |
| #define MAX_NODES_TO_TRY 2 | |
| static struct bteinfo_s *bte_if_on_node(nasid_t nasid, int interface) | |
| { | |
| nodepda_t *tmp_nodepda; | |
| if (nasid_to_cnodeid(nasid) == -1) | |
| return (struct bteinfo_s *)NULL; | |
| tmp_nodepda = NODEPDA(nasid_to_cnodeid(nasid)); | |
| return &tmp_nodepda->bte_if[interface]; | |
| } | |
| static inline void bte_start_transfer(struct bteinfo_s *bte, u64 len, u64 mode) | |
| { | |
| if (is_shub2()) { | |
| BTE_CTRL_STORE(bte, (IBLS_BUSY | ((len) | (mode) << 24))); | |
| } else { | |
| BTE_LNSTAT_STORE(bte, len); | |
| BTE_CTRL_STORE(bte, mode); | |
| } | |
| } | |
| /************************************************************************ | |
| * Block Transfer Engine copy related functions. | |
| * | |
| ***********************************************************************/ | |
| /* | |
| * bte_copy(src, dest, len, mode, notification) | |
| * | |
| * Use the block transfer engine to move kernel memory from src to dest | |
| * using the assigned mode. | |
| * | |
| * Parameters: | |
| * src - physical address of the transfer source. | |
| * dest - physical address of the transfer destination. | |
| * len - number of bytes to transfer from source to dest. | |
| * mode - hardware defined. See reference information | |
| * for IBCT0/1 in the SHUB Programmers Reference | |
| * notification - kernel virtual address of the notification cache | |
| * line. If NULL, the default is used and | |
| * the bte_copy is synchronous. | |
| * | |
| * NOTE: This function requires src, dest, and len to | |
| * be cacheline aligned. | |
| */ | |
| bte_result_t bte_copy(u64 src, u64 dest, u64 len, u64 mode, void *notification) | |
| { | |
| u64 transfer_size; | |
| u64 transfer_stat; | |
| u64 notif_phys_addr; | |
| struct bteinfo_s *bte; | |
| bte_result_t bte_status; | |
| unsigned long irq_flags; | |
| unsigned long itc_end = 0; | |
| int nasid_to_try[MAX_NODES_TO_TRY]; | |
| int my_nasid = cpuid_to_nasid(raw_smp_processor_id()); | |
| int bte_if_index, nasid_index; | |
| int bte_first, btes_per_node = BTES_PER_NODE; | |
| BTE_PRINTK(("bte_copy(0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%p)\n", | |
| src, dest, len, mode, notification)); | |
| if (len == 0) { | |
| return BTE_SUCCESS; | |
| } | |
| BUG_ON(len & L1_CACHE_MASK); | |
| BUG_ON(src & L1_CACHE_MASK); | |
| BUG_ON(dest & L1_CACHE_MASK); | |
| BUG_ON(len > BTE_MAX_XFER); | |
| /* | |
| * Start with interface corresponding to cpu number | |
| */ | |
| bte_first = raw_smp_processor_id() % btes_per_node; | |
| if (mode & BTE_USE_DEST) { | |
| /* try remote then local */ | |
| nasid_to_try[0] = NASID_GET(dest); | |
| if (mode & BTE_USE_ANY) { | |
| nasid_to_try[1] = my_nasid; | |
| } else { | |
| nasid_to_try[1] = 0; | |
| } | |
| } else { | |
| /* try local then remote */ | |
| nasid_to_try[0] = my_nasid; | |
| if (mode & BTE_USE_ANY) { | |
| nasid_to_try[1] = NASID_GET(dest); | |
| } else { | |
| nasid_to_try[1] = 0; | |
| } | |
| } | |
| retry_bteop: | |
| do { | |
| local_irq_save(irq_flags); | |
| bte_if_index = bte_first; | |
| nasid_index = 0; | |
| /* Attempt to lock one of the BTE interfaces. */ | |
| while (nasid_index < MAX_NODES_TO_TRY) { | |
| bte = bte_if_on_node(nasid_to_try[nasid_index],bte_if_index); | |
| if (bte == NULL) { | |
| nasid_index++; | |
| continue; | |
| } | |
| if (spin_trylock(&bte->spinlock)) { | |
| if (!(*bte->most_rcnt_na & BTE_WORD_AVAILABLE) || | |
| (BTE_LNSTAT_LOAD(bte) & BTE_ACTIVE)) { | |
| /* Got the lock but BTE still busy */ | |
| spin_unlock(&bte->spinlock); | |
| } else { | |
| /* we got the lock and it's not busy */ | |
| break; | |
| } | |
| } | |
| bte_if_index = (bte_if_index + 1) % btes_per_node; /* Next interface */ | |
| if (bte_if_index == bte_first) { | |
| /* | |
| * We've tried all interfaces on this node | |
| */ | |
| nasid_index++; | |
| } | |
| bte = NULL; | |
| } | |
| if (bte != NULL) { | |
| break; | |
| } | |
| local_irq_restore(irq_flags); | |
| if (!(mode & BTE_WACQUIRE)) { | |
| return BTEFAIL_NOTAVAIL; | |
| } | |
| } while (1); | |
| if (notification == NULL) { | |
| /* User does not want to be notified. */ | |
| bte->most_rcnt_na = &bte->notify; | |
| } else { | |
| bte->most_rcnt_na = notification; | |
| } | |
| /* Calculate the number of cache lines to transfer. */ | |
| transfer_size = ((len >> L1_CACHE_SHIFT) & BTE_LEN_MASK); | |
| /* Initialize the notification to a known value. */ | |
| *bte->most_rcnt_na = BTE_WORD_BUSY; | |
| notif_phys_addr = (u64)bte->most_rcnt_na; | |
| /* Set the source and destination registers */ | |
| BTE_PRINTKV(("IBSA = 0x%lx)\n", src)); | |
| BTE_SRC_STORE(bte, src); | |
| BTE_PRINTKV(("IBDA = 0x%lx)\n", dest)); | |
| BTE_DEST_STORE(bte, dest); | |
| /* Set the notification register */ | |
| BTE_PRINTKV(("IBNA = 0x%lx)\n", notif_phys_addr)); | |
| BTE_NOTIF_STORE(bte, notif_phys_addr); | |
| /* Initiate the transfer */ | |
| BTE_PRINTK(("IBCT = 0x%lx)\n", BTE_VALID_MODE(mode))); | |
| bte_start_transfer(bte, transfer_size, BTE_VALID_MODE(mode)); | |
| itc_end = ia64_get_itc() + (40000000 * local_cpu_data->cyc_per_usec); | |
| spin_unlock_irqrestore(&bte->spinlock, irq_flags); | |
| if (notification != NULL) { | |
| return BTE_SUCCESS; | |
| } | |
| while ((transfer_stat = *bte->most_rcnt_na) == BTE_WORD_BUSY) { | |
| cpu_relax(); | |
| if (ia64_get_itc() > itc_end) { | |
| BTE_PRINTK(("BTE timeout nasid 0x%x bte%d IBLS = 0x%lx na 0x%lx\n", | |
| NASID_GET(bte->bte_base_addr), bte->bte_num, | |
| BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na) ); | |
| bte->bte_error_count++; | |
| bte->bh_error = IBLS_ERROR; | |
| bte_error_handler(NODEPDA(bte->bte_cnode)); | |
| *bte->most_rcnt_na = BTE_WORD_AVAILABLE; | |
| goto retry_bteop; | |
| } | |
| } | |
| BTE_PRINTKV((" Delay Done. IBLS = 0x%lx, most_rcnt_na = 0x%lx\n", | |
| BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na)); | |
| if (transfer_stat & IBLS_ERROR) { | |
| bte_status = BTE_GET_ERROR_STATUS(transfer_stat); | |
| } else { | |
| bte_status = BTE_SUCCESS; | |
| } | |
| *bte->most_rcnt_na = BTE_WORD_AVAILABLE; | |
| BTE_PRINTK(("Returning status is 0x%lx and most_rcnt_na is 0x%lx\n", | |
| BTE_LNSTAT_LOAD(bte), *bte->most_rcnt_na)); | |
| return bte_status; | |
| } | |
| EXPORT_SYMBOL(bte_copy); | |
| /* | |
| * bte_unaligned_copy(src, dest, len, mode) | |
| * | |
| * use the block transfer engine to move kernel | |
| * memory from src to dest using the assigned mode. | |
| * | |
| * Parameters: | |
| * src - physical address of the transfer source. | |
| * dest - physical address of the transfer destination. | |
| * len - number of bytes to transfer from source to dest. | |
| * mode - hardware defined. See reference information | |
| * for IBCT0/1 in the SGI documentation. | |
| * | |
| * NOTE: If the source, dest, and len are all cache line aligned, | |
| * then it would be _FAR_ preferable to use bte_copy instead. | |
| */ | |
| bte_result_t bte_unaligned_copy(u64 src, u64 dest, u64 len, u64 mode) | |
| { | |
| int destFirstCacheOffset; | |
| u64 headBteSource; | |
| u64 headBteLen; | |
| u64 headBcopySrcOffset; | |
| u64 headBcopyDest; | |
| u64 headBcopyLen; | |
| u64 footBteSource; | |
| u64 footBteLen; | |
| u64 footBcopyDest; | |
| u64 footBcopyLen; | |
| bte_result_t rv; | |
| char *bteBlock, *bteBlock_unaligned; | |
| if (len == 0) { | |
| return BTE_SUCCESS; | |
| } | |
| /* temporary buffer used during unaligned transfers */ | |
| bteBlock_unaligned = kmalloc(len + 3 * L1_CACHE_BYTES, GFP_KERNEL); | |
| if (bteBlock_unaligned == NULL) { | |
| return BTEFAIL_NOTAVAIL; | |
| } | |
| bteBlock = (char *)L1_CACHE_ALIGN((u64) bteBlock_unaligned); | |
| headBcopySrcOffset = src & L1_CACHE_MASK; | |
| destFirstCacheOffset = dest & L1_CACHE_MASK; | |
| /* | |
| * At this point, the transfer is broken into | |
| * (up to) three sections. The first section is | |
| * from the start address to the first physical | |
| * cache line, the second is from the first physical | |
| * cache line to the last complete cache line, | |
| * and the third is from the last cache line to the | |
| * end of the buffer. The first and third sections | |
| * are handled by bte copying into a temporary buffer | |
| * and then bcopy'ing the necessary section into the | |
| * final location. The middle section is handled with | |
| * a standard bte copy. | |
| * | |
| * One nasty exception to the above rule is when the | |
| * source and destination are not symmetrically | |
| * mis-aligned. If the source offset from the first | |
| * cache line is different from the destination offset, | |
| * we make the first section be the entire transfer | |
| * and the bcopy the entire block into place. | |
| */ | |
| if (headBcopySrcOffset == destFirstCacheOffset) { | |
| /* | |
| * Both the source and destination are the same | |
| * distance from a cache line boundary so we can | |
| * use the bte to transfer the bulk of the | |
| * data. | |
| */ | |
| headBteSource = src & ~L1_CACHE_MASK; | |
| headBcopyDest = dest; | |
| if (headBcopySrcOffset) { | |
| headBcopyLen = | |
| (len > | |
| (L1_CACHE_BYTES - | |
| headBcopySrcOffset) ? L1_CACHE_BYTES | |
| - headBcopySrcOffset : len); | |
| headBteLen = L1_CACHE_BYTES; | |
| } else { | |
| headBcopyLen = 0; | |
| headBteLen = 0; | |
| } | |
| if (len > headBcopyLen) { | |
| footBcopyLen = (len - headBcopyLen) & L1_CACHE_MASK; | |
| footBteLen = L1_CACHE_BYTES; | |
| footBteSource = src + len - footBcopyLen; | |
| footBcopyDest = dest + len - footBcopyLen; | |
| if (footBcopyDest == (headBcopyDest + headBcopyLen)) { | |
| /* | |
| * We have two contiguous bcopy | |
| * blocks. Merge them. | |
| */ | |
| headBcopyLen += footBcopyLen; | |
| headBteLen += footBteLen; | |
| } else if (footBcopyLen > 0) { | |
| rv = bte_copy(footBteSource, | |
| ia64_tpa((unsigned long)bteBlock), | |
| footBteLen, mode, NULL); | |
| if (rv != BTE_SUCCESS) { | |
| kfree(bteBlock_unaligned); | |
| return rv; | |
| } | |
| memcpy(__va(footBcopyDest), | |
| (char *)bteBlock, footBcopyLen); | |
| } | |
| } else { | |
| footBcopyLen = 0; | |
| footBteLen = 0; | |
| } | |
| if (len > (headBcopyLen + footBcopyLen)) { | |
| /* now transfer the middle. */ | |
| rv = bte_copy((src + headBcopyLen), | |
| (dest + | |
| headBcopyLen), | |
| (len - headBcopyLen - | |
| footBcopyLen), mode, NULL); | |
| if (rv != BTE_SUCCESS) { | |
| kfree(bteBlock_unaligned); | |
| return rv; | |
| } | |
| } | |
| } else { | |
| /* | |
| * The transfer is not symmetric, we will | |
| * allocate a buffer large enough for all the | |
| * data, bte_copy into that buffer and then | |
| * bcopy to the destination. | |
| */ | |
| headBcopySrcOffset = src & L1_CACHE_MASK; | |
| headBcopyDest = dest; | |
| headBcopyLen = len; | |
| headBteSource = src - headBcopySrcOffset; | |
| /* Add the leading and trailing bytes from source */ | |
| headBteLen = L1_CACHE_ALIGN(len + headBcopySrcOffset); | |
| } | |
| if (headBcopyLen > 0) { | |
| rv = bte_copy(headBteSource, | |
| ia64_tpa((unsigned long)bteBlock), headBteLen, | |
| mode, NULL); | |
| if (rv != BTE_SUCCESS) { | |
| kfree(bteBlock_unaligned); | |
| return rv; | |
| } | |
| memcpy(__va(headBcopyDest), ((char *)bteBlock + | |
| headBcopySrcOffset), headBcopyLen); | |
| } | |
| kfree(bteBlock_unaligned); | |
| return BTE_SUCCESS; | |
| } | |
| EXPORT_SYMBOL(bte_unaligned_copy); | |
| /************************************************************************ | |
| * Block Transfer Engine initialization functions. | |
| * | |
| ***********************************************************************/ | |
| static void bte_recovery_timeout(struct timer_list *t) | |
| { | |
| struct nodepda_s *nodepda = from_timer(nodepda, t, bte_recovery_timer); | |
| bte_error_handler(nodepda); | |
| } | |
| /* | |
| * bte_init_node(nodepda, cnode) | |
| * | |
| * Initialize the nodepda structure with BTE base addresses and | |
| * spinlocks. | |
| */ | |
| void bte_init_node(nodepda_t * mynodepda, cnodeid_t cnode) | |
| { | |
| int i; | |
| /* | |
| * Indicate that all the block transfer engines on this node | |
| * are available. | |
| */ | |
| /* | |
| * Allocate one bte_recover_t structure per node. It holds | |
| * the recovery lock for node. All the bte interface structures | |
| * will point at this one bte_recover structure to get the lock. | |
| */ | |
| spin_lock_init(&mynodepda->bte_recovery_lock); | |
| timer_setup(&mynodepda->bte_recovery_timer, bte_recovery_timeout, 0); | |
| for (i = 0; i < BTES_PER_NODE; i++) { | |
| u64 *base_addr; | |
| /* Which link status register should we use? */ | |
| base_addr = (u64 *) | |
| REMOTE_HUB_ADDR(cnodeid_to_nasid(cnode), BTE_BASE_ADDR(i)); | |
| mynodepda->bte_if[i].bte_base_addr = base_addr; | |
| mynodepda->bte_if[i].bte_source_addr = BTE_SOURCE_ADDR(base_addr); | |
| mynodepda->bte_if[i].bte_destination_addr = BTE_DEST_ADDR(base_addr); | |
| mynodepda->bte_if[i].bte_control_addr = BTE_CTRL_ADDR(base_addr); | |
| mynodepda->bte_if[i].bte_notify_addr = BTE_NOTIF_ADDR(base_addr); | |
| /* | |
| * Initialize the notification and spinlock | |
| * so the first transfer can occur. | |
| */ | |
| mynodepda->bte_if[i].most_rcnt_na = | |
| &(mynodepda->bte_if[i].notify); | |
| mynodepda->bte_if[i].notify = BTE_WORD_AVAILABLE; | |
| spin_lock_init(&mynodepda->bte_if[i].spinlock); | |
| mynodepda->bte_if[i].bte_cnode = cnode; | |
| mynodepda->bte_if[i].bte_error_count = 0; | |
| mynodepda->bte_if[i].bte_num = i; | |
| mynodepda->bte_if[i].cleanup_active = 0; | |
| mynodepda->bte_if[i].bh_error = 0; | |
| } | |
| } |