text: /* $Id$
   *
   * 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) 1992-1997, 2000-2003 Silicon Graphics, Inc.  All Rights Reserved.
   */
  
  #ident  "$Revision: 1.167 $"
  
  #include <linux/types.h>
  #include <linux/slab.h>
  #include <linux/interrupt.h>
  #include <asm/smp.h>
  #include <asm/irq.h>
  #include <asm/hw_irq.h>
  #include <asm/system.h>
  #include <asm/sn/sgi.h>
  #include <asm/sn/iograph.h>
  #include <asm/sn/invent.h>
  #include <asm/sn/hcl.h>
  #include <asm/sn/labelcl.h>
  #include <asm/sn/io.h>
  #include <asm/sn/sn_private.h>
  #include <asm/sn/klconfig.h>
  #include <asm/sn/sn_cpuid.h>
  #include <asm/sn/pci/pciio.h>
  #include <asm/sn/pci/pcibr.h>
  #include <asm/sn/xtalk/xtalk.h>
  #include <asm/sn/pci/pcibr_private.h>
  #include <asm/sn/intr.h>
  #include <asm/sn/sn2/shub_mmr_t.h>
  #include <asm/sal.h>
  #include <asm/sn/sn_sal.h>
  #include <asm/sn/sndrv.h>
  #include <asm/sn/sn2/shubio.h>
  #include <asm/sn/sn2/shub_mmr.h>
  
  /*
   * Shub WAR for Xbridge Little Endian problem:
   *	Xbridge has to run in BIG ENDIAN even with Shub.
   */
  
  
  /*
   * io_sh_swapper: Turn on Shub byte swapping.
   *	All data destined to and from Shub to XIO are byte-swapped.
   */
  void
  io_sh_swapper(nasid_t nasid, int onoff)
  {
      ii_iwc_u_t      ii_iwc;
  
      ii_iwc.ii_iwc_regval = REMOTE_HUB_L(nasid, IIO_IWC);
  
      ii_iwc.ii_iwc_fld_s.i_dma_byte_swap = onoff;
      REMOTE_HUB_S(nasid, IIO_IWC, ii_iwc.ii_iwc_regval);
      ii_iwc.ii_iwc_regval = REMOTE_HUB_L(nasid, IIO_IWC);
  
  }
  
  /*
   * io_get_sh_swapper: Return current Swap mode.
   *	1 = Swap on, 0 = Swap off.
   */
  int
  io_get_sh_swapper(nasid_t nasid)
  {
      ii_iwc_u_t      ii_iwc;
  
      ii_iwc.ii_iwc_regval = REMOTE_HUB_L(nasid, IIO_IWC);
      return(ii_iwc.ii_iwc_fld_s.i_dma_byte_swap);
  
  }
  
  #define SHUB_NUM_ECF_REGISTERS 8
  
  static uint32_t	shub_perf_counts[SHUB_NUM_ECF_REGISTERS];
  
  static shubreg_t shub_perf_counts_regs[SHUB_NUM_ECF_REGISTERS] = {
  	SH_PERFORMANCE_COUNTER0,
  	SH_PERFORMANCE_COUNTER1,
  	SH_PERFORMANCE_COUNTER2,
  	SH_PERFORMANCE_COUNTER3,
  	SH_PERFORMANCE_COUNTER4,
  	SH_PERFORMANCE_COUNTER5,
  	SH_PERFORMANCE_COUNTER6,
  	SH_PERFORMANCE_COUNTER7
  };
  
  static inline void
  shub_mmr_write(cnodeid_t cnode, shubreg_t reg, uint64_t val)
  {
  	int		   nasid = cnodeid_to_nasid(cnode);
  	volatile uint64_t *addr = (uint64_t *)(GLOBAL_MMR_ADDR(nasid, reg));
  
  	*addr = val;
  	__ia64_mf_a();
  }
  
  static inline void
  shub_mmr_write_iospace(cnodeid_t cnode, shubreg_t reg, uint64_t val)
  {
  	int		   nasid = cnodeid_to_nasid(cnode);
  
  	REMOTE_HUB_S(nasid, reg, val);
  }
  
  static inline void
  shub_mmr_write32(cnodeid_t cnode, shubreg_t reg, uint32_t val)
  {
  	int		   nasid = cnodeid_to_nasid(cnode);
  	volatile uint32_t *addr = (uint32_t *)(GLOBAL_MMR_ADDR(nasid, reg));
  
  	*addr = val;
  	__ia64_mf_a();
  }
  
  static inline uint64_t
  shub_mmr_read(cnodeid_t cnode, shubreg_t reg)
  {
  	int		  nasid = cnodeid_to_nasid(cnode);
  	volatile uint64_t val;
  
  	val = *(uint64_t *)(GLOBAL_MMR_ADDR(nasid, reg));
  	__ia64_mf_a();
  
  	return val;
  }
  
  static inline uint64_t
  shub_mmr_read_iospace(cnodeid_t cnode, shubreg_t reg)
  {
  	int		  nasid = cnodeid_to_nasid(cnode);
  
  	return REMOTE_HUB_L(nasid, reg);
  }
  
  static inline uint32_t
  shub_mmr_read32(cnodeid_t cnode, shubreg_t reg)
  {
  	int		  nasid = cnodeid_to_nasid(cnode);
  	volatile uint32_t val;
  
  	val = *(uint32_t *)(GLOBAL_MMR_ADDR(nasid, reg));
  	__ia64_mf_a();
  
  	return val;
  }
  
  static int
  reset_shub_stats(cnodeid_t cnode)
  {
  	int i;
  
  	for (i=0; i < SHUB_NUM_ECF_REGISTERS; i++) {
  		shub_perf_counts[i] = 0;
  		shub_mmr_write32(cnode, shub_perf_counts_regs[i], 0);
  	}
  	return 0;
  }
  
  static int
  configure_shub_stats(cnodeid_t cnode, unsigned long arg)
  {
  	uint64_t	*p = (uint64_t *)arg;
  	uint64_t	i;
  	uint64_t	regcnt;
  	uint64_t	regval[2];
  
  	if (copy_from_user((void *)&regcnt, p, sizeof(regcnt)))
  	    return -EFAULT;
  
  	for (p++, i=0; i < regcnt; i++, p += 2) {
  		if (copy_from_user((void *)regval, (void *)p, sizeof(regval)))
  		    return -EFAULT;
  		if (regval[0] & 0x7) {
  		    printk("Error: configure_shub_stats: unaligned address 0x%016lx\n", regval[0]);
  		    return -EINVAL;
  		}
  		shub_mmr_write(cnode, (shubreg_t)regval[0], regval[1]);
  	}
  	return 0;
  }
  
  static int
  capture_shub_stats(cnodeid_t cnode, uint32_t *counts)
  {
  	int 		i;
  
  	for (i=0; i < SHUB_NUM_ECF_REGISTERS; i++) {
  		counts[i] = shub_mmr_read32(cnode, shub_perf_counts_regs[i]);
  	}
  	return 0;
  }
  
  static int
  shubstats_ioctl(struct inode *inode, struct file *file,
          unsigned int cmd, unsigned long arg)
  {
          cnodeid_t       cnode;
          uint64_t        longarg;
  	int		nasid;
  
          cnode = (cnodeid_t)file->f_dentry->d_fsdata;
  
          switch (cmd) {
  	case SNDRV_SHUB_CONFIGURE:
  		return configure_shub_stats(cnode, arg);
  		break;
  
  	case SNDRV_SHUB_RESETSTATS:
  		reset_shub_stats(cnode);
  		break;
  
  	case SNDRV_SHUB_INFOSIZE:
  		longarg = sizeof(shub_perf_counts);
  		if (copy_to_user((void *)arg, &longarg, sizeof(longarg))) {
  		    return -EFAULT;
  		}
  		break;
  
  	case SNDRV_SHUB_GETSTATS:
  		capture_shub_stats(cnode, shub_perf_counts);
  		if (copy_to_user((void *)arg, shub_perf_counts,
  				       	sizeof(shub_perf_counts))) {
  		    return -EFAULT;
  		}
  		break;
  
  	case SNDRV_SHUB_GETNASID:
  		nasid = cnodeid_to_nasid(cnode);
  		if (copy_to_user((void *)arg, &nasid,
  				       	sizeof(nasid))) {
  		    return -EFAULT;
  		}
  		break;
  
  	default:
  		return -EINVAL;
  	}
  
  	return 0;
  }
  
  struct file_operations shub_mon_fops = {
  	        .ioctl          = shubstats_ioctl,
  };
  
  /*
   * "linkstatd" kernel thread to export SGI Numalink
   * stats via /proc/sgi_sn/linkstats
   */
  static struct s_linkstats {
  	uint64_t	hs_ni_sn_errors[2];
  	uint64_t	hs_ni_cb_errors[2];
  	uint64_t	hs_ni_retry_errors[2];
  	int		hs_ii_up;
  	uint64_t	hs_ii_sn_errors;
  	uint64_t	hs_ii_cb_errors;
  	uint64_t	hs_ii_retry_errors;
  } *sn_linkstats;
  
  static spinlock_t    sn_linkstats_lock;
  static unsigned long sn_linkstats_starttime;
  static unsigned long sn_linkstats_samples;
  static unsigned long sn_linkstats_overflows;
  static unsigned long sn_linkstats_update_msecs;
  
  void
  sn_linkstats_reset(unsigned long msecs)
  {
  	int		    cnode;
  	uint64_t	    iio_wstat;
  	uint64_t	    llp_csr_reg;
  
  	spin_lock(&sn_linkstats_lock);
  	memset(sn_linkstats, 0, numnodes * sizeof(struct s_linkstats));
  	for (cnode=0; cnode < numnodes; cnode++) {
  	    shub_mmr_write(cnode, SH_NI0_LLP_ERR, 0L);
  	    shub_mmr_write(cnode, SH_NI1_LLP_ERR, 0L);
  	    shub_mmr_write_iospace(cnode, IIO_LLP_LOG, 0L);
  
  	    /* zero the II retry counter */
  	    iio_wstat = shub_mmr_read_iospace(cnode, IIO_WSTAT);
  	    iio_wstat &= 0xffffffffff00ffff; /* bits 23:16 */
  	    shub_mmr_write_iospace(cnode, IIO_WSTAT, iio_wstat);
  
  	    /* Check if the II xtalk link is working */
  	    llp_csr_reg = shub_mmr_read_iospace(cnode, IIO_LLP_CSR);
  	    if (llp_csr_reg & IIO_LLP_CSR_IS_UP)
  		sn_linkstats[cnode].hs_ii_up = 1;
  	}
  
      	sn_linkstats_update_msecs = msecs;
  	sn_linkstats_samples = 0;
  	sn_linkstats_overflows = 0;
  	sn_linkstats_starttime = jiffies;
  	spin_unlock(&sn_linkstats_lock);
  }
  
  int
  linkstatd_thread(void *unused)
  {
  	int		    cnode;
  	int		    overflows;
  	uint64_t	    reg[2];
  	uint64_t	    iio_wstat = 0L;
  	ii_illr_u_t	    illr;
  	struct s_linkstats  *lsp;
  	struct task_struct  *tsk = current;
  
  	daemonize("linkstatd");
  	set_user_nice(tsk, 19);
  	sigfillset(&tsk->blocked);
  	strcpy(tsk->comm, "linkstatd");
  
  	while(1) {
  		set_current_state(TASK_INTERRUPTIBLE);
  		schedule_timeout(sn_linkstats_update_msecs * HZ / 1000);
  
  		spin_lock(&sn_linkstats_lock);
  
  		overflows = 0;
  		for (lsp=sn_linkstats, cnode=0; cnode < numnodes; cnode++, lsp++) {
  			reg[0] = shub_mmr_read(cnode, SH_NI0_LLP_ERR);
  			reg[1] = shub_mmr_read(cnode, SH_NI1_LLP_ERR);
  			if (lsp->hs_ii_up) {
  			    illr = (ii_illr_u_t)shub_mmr_read_iospace(cnode, IIO_LLP_LOG);
  			    iio_wstat = shub_mmr_read_iospace(cnode, IIO_WSTAT);
  			}
  
  			if (!overflows && (
  			    (reg[0] & SH_NI0_LLP_ERR_RX_SN_ERR_COUNT_MASK) == 
  				     SH_NI0_LLP_ERR_RX_SN_ERR_COUNT_MASK ||
  			    (reg[0] & SH_NI0_LLP_ERR_RX_CB_ERR_COUNT_MASK) ==
  			             SH_NI0_LLP_ERR_RX_CB_ERR_COUNT_MASK ||
  			    (reg[1] & SH_NI1_LLP_ERR_RX_SN_ERR_COUNT_MASK) ==
  			             SH_NI1_LLP_ERR_RX_SN_ERR_COUNT_MASK ||
  			    (reg[1] & SH_NI1_LLP_ERR_RX_CB_ERR_COUNT_MASK) ==
  			             SH_NI1_LLP_ERR_RX_CB_ERR_COUNT_MASK ||
  			    (lsp->hs_ii_up && illr.ii_illr_fld_s.i_sn_cnt == IIO_LLP_SN_MAX) ||
  			    (lsp->hs_ii_up && illr.ii_illr_fld_s.i_cb_cnt == IIO_LLP_CB_MAX))) {
  			    overflows = 1;
  			}
  
  #define LINKSTAT_UPDATE(reg, cnt, mask, shift) cnt += (reg & mask) >> shift
  
  			LINKSTAT_UPDATE(reg[0], lsp->hs_ni_sn_errors[0],
  					SH_NI0_LLP_ERR_RX_SN_ERR_COUNT_MASK,
  					SH_NI0_LLP_ERR_RX_SN_ERR_COUNT_SHFT);
  
  			LINKSTAT_UPDATE(reg[1], lsp->hs_ni_sn_errors[1],
  					SH_NI1_LLP_ERR_RX_SN_ERR_COUNT_MASK,
  					SH_NI1_LLP_ERR_RX_SN_ERR_COUNT_SHFT);
  
  			LINKSTAT_UPDATE(reg[0], lsp->hs_ni_cb_errors[0],
  					SH_NI0_LLP_ERR_RX_CB_ERR_COUNT_MASK,
  					SH_NI0_LLP_ERR_RX_CB_ERR_COUNT_SHFT);
  
  			LINKSTAT_UPDATE(reg[1], lsp->hs_ni_cb_errors[1],
  					SH_NI1_LLP_ERR_RX_CB_ERR_COUNT_MASK,
  					SH_NI1_LLP_ERR_RX_CB_ERR_COUNT_SHFT);
  
  			LINKSTAT_UPDATE(reg[0], lsp->hs_ni_retry_errors[0],
  					SH_NI0_LLP_ERR_RETRY_COUNT_MASK,
  					SH_NI0_LLP_ERR_RETRY_COUNT_SHFT);
  
  			LINKSTAT_UPDATE(reg[1], lsp->hs_ni_retry_errors[1],
  					SH_NI1_LLP_ERR_RETRY_COUNT_MASK,
  					SH_NI1_LLP_ERR_RETRY_COUNT_SHFT);
  
  			if (lsp->hs_ii_up) {
  			    /* II sn and cb errors */
  			    lsp->hs_ii_sn_errors += illr.ii_illr_fld_s.i_sn_cnt;
  			    lsp->hs_ii_cb_errors += illr.ii_illr_fld_s.i_cb_cnt;
  			    lsp->hs_ii_retry_errors += (iio_wstat & 0x0000000000ff0000) >> 16;
  
  			    shub_mmr_write(cnode, SH_NI0_LLP_ERR, 0L);
  			    shub_mmr_write(cnode, SH_NI1_LLP_ERR, 0L);
  			    shub_mmr_write_iospace(cnode, IIO_LLP_LOG, 0L);
  
  			    /* zero the II retry counter */
  			    iio_wstat = shub_mmr_read_iospace(cnode, IIO_WSTAT);
  			    iio_wstat &= 0xffffffffff00ffff; /* bits 23:16 */
  			    shub_mmr_write_iospace(cnode, IIO_WSTAT, iio_wstat);
  			}
  		}
  
  		sn_linkstats_samples++;
  		if (overflows)
  		    sn_linkstats_overflows++;
  
  		spin_unlock(&sn_linkstats_lock);
  	}
  }
  
  static char *
  rate_per_minute(uint64_t val, uint64_t secs)
  {
  	static char	buf[16];
  	uint64_t	a=0, b=0, c=0, d=0;
  
  	if (secs) {
  		a = 60 * val / secs;
  		b = 60 * 10 * val / secs - (10 * a);
  		c = 60 * 100 * val / secs - (100 * a) - (10 * b);
  		d = 60 * 1000 * val / secs - (1000 * a) - (100 * b) - (10 * c);
  	}
  	sprintf(buf, "%4lu.%lu%lu%lu", a, b, c, d);
  
  	return buf;
  }
  
  int
  sn_linkstats_get(char *page)
  {
  	int			n = 0;
  	int			cnode;
  	int			nlport;
  	struct s_linkstats	*lsp;
  	nodepda_t		*npda;
  	uint64_t	    	snsum = 0;
  	uint64_t	    	cbsum = 0;
  	uint64_t	    	retrysum = 0;
  	uint64_t	    	snsum_ii = 0;
  	uint64_t	    	cbsum_ii = 0;
  	uint64_t	    	retrysum_ii = 0;
  	uint64_t		secs;
  
  	spin_lock(&sn_linkstats_lock);
  	secs = (jiffies - sn_linkstats_starttime) / HZ;
  
  	n += sprintf(page, "# SGI Numalink stats v1 : %lu samples, %lu o/flows, update %lu msecs\n",
  		sn_linkstats_samples, sn_linkstats_overflows, sn_linkstats_update_msecs);
  
  	n += sprintf(page+n, "%-37s %8s %8s %8s %8s\n",
  		"# Numalink", "sn errs", "cb errs", "cb/min", "retries");
  
  	for (lsp=sn_linkstats, cnode=0; cnode < numnodes; cnode++, lsp++) {
  		npda = NODEPDA(cnode);
  
  		/* two NL links on each SHub */
  		for (nlport=0; nlport < 2; nlport++) {
  			cbsum += lsp->hs_ni_cb_errors[nlport];
  			snsum += lsp->hs_ni_sn_errors[nlport];
  			retrysum += lsp->hs_ni_retry_errors[nlport];
  
  			/* avoid buffer overrun (should be using seq_read API) */
  			if (numnodes > 64)
  				continue;
  
  			n += sprintf(page + n, "/%s/link/%d  %8lu %8lu %8s %8lu\n",
  			    npda->hwg_node_name, nlport+1, lsp->hs_ni_sn_errors[nlport],
  			    lsp->hs_ni_cb_errors[nlport], 
  			    rate_per_minute(lsp->hs_ni_cb_errors[nlport], secs),
  			    lsp->hs_ni_retry_errors[nlport]);
  		}
  
  		/* one II port on each SHub (may not be connected) */
  		if (lsp->hs_ii_up) {
  		    n += sprintf(page + n, "/%s/xtalk   %8lu %8lu %8s %8lu\n",
  			npda->hwg_node_name, lsp->hs_ii_sn_errors,
  			lsp->hs_ii_cb_errors, rate_per_minute(lsp->hs_ii_cb_errors, secs),
  			lsp->hs_ii_retry_errors);
  
  		    snsum_ii += lsp->hs_ii_sn_errors;
  		    cbsum_ii += lsp->hs_ii_cb_errors;
  		    retrysum_ii += lsp->hs_ii_retry_errors;
  		}
  	}
  
  	n += sprintf(page + n, "%-37s %8lu %8lu %8s %8lu\n",
  		"System wide NL totals", snsum, cbsum, 
  		rate_per_minute(cbsum, secs), retrysum);
  
  	n += sprintf(page + n, "%-37s %8lu %8lu %8s %8lu\n",
  		"System wide II totals", snsum_ii, cbsum_ii, 
  		rate_per_minute(cbsum_ii, secs), retrysum_ii);
  
  	spin_unlock(&sn_linkstats_lock);
  
  	return n;
  }
  
  static int __init
  linkstatd_init(void)
  {
  	if (!ia64_platform_is("sn2"))
  		return -ENODEV;
  
  	spin_lock_init(&sn_linkstats_lock);
  	sn_linkstats = kmalloc(numnodes * sizeof(struct s_linkstats), GFP_KERNEL);
  	sn_linkstats_reset(60000UL); /* default 60 second update interval */
  	kernel_thread(linkstatd_thread, NULL, CLONE_KERNEL);
  
  	return 0;                                                                       
  }
  
  __initcall(linkstatd_init);
  