| /* |
| * 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/memblock.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; |
| } |
| |
| } |