/*
 * Linux OS Independent Layer
 *
 * Copyright 2007, Broadcom Corporation
 * All Rights Reserved.
 * 
 * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
 * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
 * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
 *
 * $Id$
 */

#define LINUX_OSL

#include <typedefs.h>
#include <bcmendian.h>
#include <linuxver.h>
#include <bcmdefs.h>
#include <osl.h>
#include "linux_osl.h"
#include "bcmutils.h"
#include <linux/delay.h>
#ifdef mips
#include <asm/paccess.h>
#endif /* mips */
#include <pcicfg.h>

#define PCI_CFG_RETRY 		10

#define OS_HANDLE_MAGIC		0x1234abcd	/* Magic # to recognise osh */
#define BCM_MEM_FILENAME_LEN 	24	/* Mem. filename length */

typedef struct bcm_mem_link
{
  struct bcm_mem_link *prev;
  struct bcm_mem_link *next;
  uint size;
  int line;
  char file[BCM_MEM_FILENAME_LEN];
} bcm_mem_link_t;

#if 0
struct osl_info
{
  osl_pubinfo_t pub;
  uint magic;
  void *pdev;
  uint malloced;
  uint failed;
  uint bustype;
  bcm_mem_link_t *dbgmem_list;
#ifdef BCMDBG_PKT		/* pkt logging for debugging */
  pktlist_info_t pktlist;
#endif				/* BCMDBG_PKT */
};
#endif

static int16 linuxbcmerrormap[] = { 0,	/* 0 */
  -EINVAL,			/* BCME_ERROR */
  -EINVAL,			/* BCME_BADARG */
  -EINVAL,			/* BCME_BADOPTION */
  -EINVAL,			/* BCME_NOTUP */
  -EINVAL,			/* BCME_NOTDOWN */
  -EINVAL,			/* BCME_NOTAP */
  -EINVAL,			/* BCME_NOTSTA */
  -EINVAL,			/* BCME_BADKEYIDX */
  -EINVAL,			/* BCME_RADIOOFF */
  -EINVAL,			/* BCME_NOTBANDLOCKED */
  -EINVAL,			/* BCME_NOCLK */
  -EINVAL,			/* BCME_BADRATESET */
  -EINVAL,			/* BCME_BADBAND */
  -E2BIG,			/* BCME_BUFTOOSHORT */
  -E2BIG,			/* BCME_BUFTOOLONG */
  -EBUSY,			/* BCME_BUSY */
  -EINVAL,			/* BCME_NOTASSOCIATED */
  -EINVAL,			/* BCME_BADSSIDLEN */
  -EINVAL,			/* BCME_OUTOFRANGECHAN */
  -EINVAL,			/* BCME_BADCHAN */
  -EFAULT,			/* BCME_BADADDR */
  -ENOMEM,			/* BCME_NORESOURCE */
  -EOPNOTSUPP,			/* BCME_UNSUPPORTED */
  -EMSGSIZE,			/* BCME_BADLENGTH */
  -EINVAL,			/* BCME_NOTREADY */
  -EPERM,			/* BCME_NOTPERMITTED */
  -ENOMEM,			/* BCME_NOMEM */
  -EINVAL,			/* BCME_ASSOCIATED */
  -ERANGE,			/* BCME_RANGE */
  -EINVAL,			/* BCME_NOTFOUND */
  -EINVAL,			/* BCME_WME_NOT_ENABLED */
  -EINVAL,			/* BCME_TSPEC_NOTFOUND */
  -EINVAL,			/* BCME_ACM_NOTSUPPORTED */
  -EINVAL,			/* BCME_NOT_WME_ASSOCIATION */
  -EIO,				/* BCME_SDIO_ERROR */
  -ENODEV,			/* BCME_DONGLE_DOWN */
  -EINVAL			/* BCME_VERSION */
/* When an new error code is added to bcmutils.h, add os 
 * spcecific error translation here as well
 */
/* check if BCME_LAST changed since the last time this function was updated */
#if BCME_LAST != -37
#error "You need to add a OS error translation in the linuxbcmerrormap \
	for new error code defined in bcmuitls.h"
#endif /* BCME_LAST != -37 */
};

/* translate bcmerrors into linux errors */
int
osl_error (int bcmerror)
{
  if (bcmerror > 0)
    bcmerror = 0;
  else if (bcmerror < BCME_LAST)
    bcmerror = BCME_ERROR;

  /* Array bounds covered by ASSERT in osl_attach */
  return linuxbcmerrormap[-bcmerror];
}

osl_t *
osl_attach (void *pdev, uint bustype, bool pkttag)
{
  osl_t *osh;

  osh = kmalloc (sizeof (osl_t), GFP_ATOMIC);
  ASSERT (osh);

  bzero (osh, sizeof (osl_t));

  /* Check that error map has the right number of entries in it */
  ASSERT (ABS (BCME_LAST) == (ARRAYSIZE (linuxbcmerrormap) - 1));

  osh->magic = OS_HANDLE_MAGIC;
  osh->malloced = 0;
  osh->failed = 0;
  osh->dbgmem_list = NULL;
  osh->pdev = pdev;
  osh->pub.pkttag = pkttag;
  osh->bustype = bustype;

  switch (bustype)
    {
    case PCI_BUS:
    case SB_BUS:
    case PCMCIA_BUS:
      osh->pub.mmbus = TRUE;
      break;
    case JTAG_BUS:
    case SDIO_BUS:
      break;
    default:
      ASSERT (FALSE);
      break;
    }

#ifdef BCMDBG
  if (pkttag)
    {
      struct sk_buff *skb;
      ASSERT (OSL_PKTTAG_SZ <= sizeof (skb->cb));
    }
#endif
  return osh;
}

void
osl_detach (osl_t * osh)
{
  if (osh == NULL)
    return;

  ASSERT (osh->magic == OS_HANDLE_MAGIC);
  kfree (osh);
}

/* Return a new packet. zero out pkttag */
void *
osl_pktget (osl_t * osh, uint len)
{
  struct sk_buff *skb;

  if ((skb = dev_alloc_skb (len)))
    {
      skb_put (skb, len);
      skb->priority = 0;

#ifdef BCMDBG_PKT
      pktlist_add (&(osh->pktlist), (void *) skb);
#endif /* BCMDBG_PKT */

      osh->pub.pktalloced++;
    }

  return ((void *) skb);
}

/* Free the driver packet. Free the tag if present */
void
osl_pktfree (osl_t * osh, void *p, bool send)
{
  struct sk_buff *skb, *nskb;

  skb = (struct sk_buff *) p;

  if (send && osh->pub.tx_fn)
    osh->pub.tx_fn (osh->pub.tx_ctx, p, 0);

  /* perversion: we use skb->next to chain multi-skb packets */
  while (skb)
    {
      nskb = skb->next;
      skb->next = NULL;

#ifdef BCMDBG_PKT
      pktlist_remove (&(osh->pktlist), (void *) skb);
#endif /* BCMDBG_PKT */

      if (skb->destructor)
	{
	  /* cannot kfree_skb() on hard IRQ (net/core/skbuff.c) if destructor exists
	   */
	  dev_kfree_skb_any (skb);
	}
      else
	{
	  /* can free immediately (even in_irq()) if destructor does not exist */
	  dev_kfree_skb (skb);
	}

      osh->pub.pktalloced--;

      skb = nskb;
    }
}

uint32
osl_pci_read_config (osl_t * osh, uint offset, uint size)
{
  uint val;
  uint retry = PCI_CFG_RETRY;

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  /* only 4byte access supported */
  ASSERT (size == 4);

  do
    {
      pci_read_config_dword (osh->pdev, offset, &val);
      if (val != 0xffffffff)
	break;
    }
  while (retry--);

#ifdef BCMDBG
  if (retry < PCI_CFG_RETRY)
    printk ("PCI CONFIG READ access to %d required %d retries\n", offset,
	    (PCI_CFG_RETRY - retry));
#endif /* BCMDBG */

  return (val);
}

void
osl_pci_write_config (osl_t * osh, uint offset, uint size, uint val)
{
  uint retry = PCI_CFG_RETRY;

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  /* only 4byte access supported */
  ASSERT (size == 4);

  do
    {
      pci_write_config_dword (osh->pdev, offset, val);
      if (offset != PCI_BAR0_WIN)
	break;
      if (osl_pci_read_config (osh, offset, size) == val)
	break;
    }
  while (retry--);

#ifdef BCMDBG
  if (retry < PCI_CFG_RETRY)
    printk ("PCI CONFIG WRITE access to %d required %d retries\n", offset,
	    (PCI_CFG_RETRY - retry));
#endif /* BCMDBG */
}

/* return bus # for the pci device pointed by osh->pdev */
uint
osl_pci_bus (osl_t * osh)
{
  ASSERT (osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev);

  return ((struct pci_dev *) osh->pdev)->bus->number;
}

/* return slot # for the pci device pointed by osh->pdev */
uint
osl_pci_slot (osl_t * osh)
{
  ASSERT (osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev);

  return PCI_SLOT (((struct pci_dev *) osh->pdev)->devfn);
}

static void
osl_pcmcia_attr (osl_t * osh, uint offset, char *buf, int size, bool write)
{
}

void
osl_pcmcia_read_attr (osl_t * osh, uint offset, void *buf, int size)
{
  osl_pcmcia_attr (osh, offset, (char *) buf, size, FALSE);
}

void
osl_pcmcia_write_attr (osl_t * osh, uint offset, void *buf, int size)
{
  osl_pcmcia_attr (osh, offset, (char *) buf, size, TRUE);
}


#ifdef BCMDBG_MEM

void *
osl_debug_malloc (osl_t * osh, uint size, int line, char *file)
{
  bcm_mem_link_t *p;
  char *basename;

  ASSERT (size);

  if ((p =
       (bcm_mem_link_t *) osl_malloc (osh,
				      sizeof (bcm_mem_link_t) + size)) ==
      NULL)
    return (NULL);

  p->size = size;
  p->line = line;

  basename = strrchr (file, '/');
  /* skip the '/' */
  if (basename)
    basename++;

  if (!basename)
    basename = file;

  strncpy (p->file, basename, BCM_MEM_FILENAME_LEN);
  p->file[BCM_MEM_FILENAME_LEN - 1] = '\0';

  /* link this block */
  p->prev = NULL;
  p->next = osh->dbgmem_list;
  if (p->next)
    p->next->prev = p;
  osh->dbgmem_list = p;

  return p + 1;
}

void
osl_debug_mfree (osl_t * osh, void *addr, uint size, int line, char *file)
{
  bcm_mem_link_t *p =
    (bcm_mem_link_t *) ((int8 *) addr - sizeof (bcm_mem_link_t));

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  if (p->size == 0)
    {
      printk
	("osl_debug_mfree: double free on addr %p size %d at line %d file %s\n",
	 addr, size, line, file);
      ASSERT (p->size);
      return;
    }

  if (p->size != size)
    {
      printk
	("osl_debug_mfree: dealloc size %d does not match alloc size %d on addr %p"
	 " at line %d file %s\n", size, p->size, addr, line, file);
      ASSERT (p->size == size);
      return;
    }

  /* unlink this block */
  if (p->prev)
    p->prev->next = p->next;
  if (p->next)
    p->next->prev = p->prev;
  if (osh->dbgmem_list == p)
    osh->dbgmem_list = p->next;
  p->next = p->prev = NULL;

  osl_mfree (osh, p, size + sizeof (bcm_mem_link_t));
}

int
osl_debug_memdump (osl_t * osh, struct bcmstrbuf *b)
{
  bcm_mem_link_t *p;

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  bcm_bprintf (b, "   Address\tSize\tFile:line\n");
  for (p = osh->dbgmem_list; p; p = p->next)
    bcm_bprintf (b, "0x%08x\t%5d\t%s:%d\n",
		 (uintptr) p + sizeof (bcm_mem_link_t), p->size, p->file,
		 p->line);

  return 0;
}

#endif /* BCMDBG_MEM */

void *
osl_malloc (osl_t * osh, uint size)
{
  void *addr;

  /* only ASSERT if osh is defined */
  if (osh)
    ASSERT (osh->magic == OS_HANDLE_MAGIC);

  if ((addr = kmalloc (size, GFP_ATOMIC)) == NULL)
    {
      if (osh)
	osh->failed++;
      return (NULL);
    }
  if (osh)
    osh->malloced += size;

  return (addr);
}

void
osl_mfree (osl_t * osh, void *addr, uint size)
{
  if (osh)
    {
      ASSERT (osh->magic == OS_HANDLE_MAGIC);
      osh->malloced -= size;
    }
  kfree (addr);
}

uint
osl_malloced (osl_t * osh)
{
  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));
  return (osh->malloced);
}

uint
osl_malloc_failed (osl_t * osh)
{
  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));
  return (osh->failed);
}

void *
osl_dma_alloc_consistent (osl_t * osh, uint size, ulong * pap)
{
  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  return (pci_alloc_consistent (osh->pdev, size, (dma_addr_t *) pap));
}

void
osl_dma_free_consistent (osl_t * osh, void *va, uint size, ulong pa)
{
  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));

  pci_free_consistent (osh->pdev, size, va, (dma_addr_t) pa);
}

uint
osl_dma_map (osl_t * osh, void *va, uint size, int direction)
{
  int dir;

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));
  dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE;
  return (pci_map_single (osh->pdev, va, size, dir));
}

void
osl_dma_unmap (osl_t * osh, uint pa, uint size, int direction)
{
  int dir;

  ASSERT ((osh && (osh->magic == OS_HANDLE_MAGIC)));
  dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE;
  pci_unmap_single (osh->pdev, (uint32) pa, size, dir);
}

#if defined(BINOSL) || defined(BCMDBG_ASSERT)
void
osl_assert (char *exp, char *file, int line)
{
  char tempbuf[255];

  sprintf (tempbuf, "assertion \"%s\" failed: file \"%s\", line %d\n", exp,
	   file, line);
  panic (tempbuf);
}
#endif /* BCMDBG_ASSERT || BINOSL */

void
osl_delay (uint usec)
{
  uint d;

  while (usec > 0)
    {
      d = MIN (usec, 1000);
      udelay (d);
      usec -= d;
    }
}

/* Clone a packet.
 * The pkttag contents are NOT cloned.
 */
void *
osl_pktdup (osl_t * osh, void *skb)
{
  void *p;

  if ((p = skb_clone ((struct sk_buff *) skb, GFP_ATOMIC)) == NULL)
    return NULL;

  /* skb_clone copies skb->cb.. we don't want that */
  if (osh->pub.pkttag)
    bzero ((void *) ((struct sk_buff *) p)->cb, OSL_PKTTAG_SZ);

  /* Increment the packet counter */
  osh->pub.pktalloced++;
#ifdef BCMDBG_PKT
  pktlist_add (&(osh->pktlist), (void *) p);
#endif /* BCMDBG_PKT */
  return (p);
}

uint
osl_pktalloced (osl_t * osh)
{
  return (osh->pub.pktalloced);
}

#ifdef BCMDBG_PKT
char *
osl_pktlist_dump (osl_t * osh, char *buf)
{
  pktlist_dump (&(osh->pktlist), buf);
  return buf;
}

void
osl_pktlist_add (osl_t * osh, void *p)
{
  pktlist_add (&(osh->pktlist), p);
}

void
osl_pktlist_remove (osl_t * osh, void *p)
{
  pktlist_remove (&(osh->pktlist), p);
}
#endif /* BCMDBG_PKT */

/*
 * BINOSL selects the slightly slower function-call-based binary compatible osl.
 */
#ifdef BINOSL

int
osl_printf (const char *format, ...)
{
  va_list args;
  char buf[1024];
  int len;

  /* sprintf into a local buffer because there *is* no "vprintk()".. */
  va_start (args, format);
  len = vsnprintf (buf, 1024, format, args);
  va_end (args);

  if (len > sizeof (buf))
    {
      printk ("osl_printf: buffer overrun\n");
      return (0);
    }

  return (printk (buf));
}

int
osl_sprintf (char *buf, const char *format, ...)
{
  va_list args;
  int rc;

  va_start (args, format);
  rc = vsprintf (buf, format, args);
  va_end (args);
  return (rc);
}

int
osl_strcmp (const char *s1, const char *s2)
{
  return (strcmp (s1, s2));
}

int
osl_strncmp (const char *s1, const char *s2, uint n)
{
  return (strncmp (s1, s2, n));
}

int
osl_strlen (const char *s)
{
  return (strlen (s));
}

char *
osl_strcpy (char *d, const char *s)
{
  return (strcpy (d, s));
}

char *
osl_strncpy (char *d, const char *s, uint n)
{
  return (strncpy (d, s, n));
}

void
bcopy (const void *src, void *dst, int len)
{
  memcpy (dst, src, len);
}

int
bcmp (const void *b1, const void *b2, int len)
{
  return (memcmp (b1, b2, len));
}

void
bzero (void *b, int len)
{
  memset (b, '\0', len);
}

uint32
osl_readl (volatile uint32 * r)
{
  return (readl (r));
}

uint16
osl_readw (volatile uint16 * r)
{
  return (readw (r));
}

uint8
osl_readb (volatile uint8 * r)
{
  return (readb (r));
}

void
osl_writel (uint32 v, volatile uint32 * r)
{
  writel (v, r);
}

void
osl_writew (uint16 v, volatile uint16 * r)
{
  writew (v, r);
}

void
osl_writeb (uint8 v, volatile uint8 * r)
{
  writeb (v, r);
}

void *
osl_uncached (void *va)
{
#ifdef mips
  return ((void *) KSEG1ADDR (va));
#else
  return ((void *) va);
#endif /* mips */
}

uint
osl_getcycles (void)
{
  uint cycles;

#if defined(mips)
  cycles = read_c0_count () * 2;
#elif defined(__i386__)
  rdtscl (cycles);
#else
  cycles = 0;
#endif /* defined(mips) */
  return cycles;
}

void *
osl_reg_map (uint32 pa, uint size)
{
  return (ioremap_nocache ((unsigned long) pa, (unsigned long) size));
}

void
osl_reg_unmap (void *va)
{
  iounmap (va);
}

int
osl_busprobe (uint32 * val, uint32 addr)
{
#ifdef mips
  return get_dbe (*val, (uint32 *) addr);
#else
  *val = readl ((uint32 *) (uintptr) addr);
  return 0;
#endif /* mips */
}

bool
osl_pktshared (void *skb)
{
  return (((struct sk_buff *) skb)->cloned);
}

uchar *
osl_pktdata (osl_t * osh, void *skb)
{
  return (((struct sk_buff *) skb)->data);
}

uint
osl_pktlen (osl_t * osh, void *skb)
{
  return (((struct sk_buff *) skb)->len);
}

uint
osl_pktheadroom (osl_t * osh, void *skb)
{
  return (uint) skb_headroom ((struct sk_buff *) skb);
}

uint
osl_pkttailroom (osl_t * osh, void *skb)
{
  return (uint) skb_tailroom ((struct sk_buff *) skb);
}

void *
osl_pktnext (osl_t * osh, void *skb)
{
  return (((struct sk_buff *) skb)->next);
}

void
osl_pktsetnext (void *skb, void *x)
{
  ((struct sk_buff *) skb)->next = (struct sk_buff *) x;
}

void
osl_pktsetlen (osl_t * osh, void *skb, uint len)
{
  __skb_trim ((struct sk_buff *) skb, len);
}

uchar *
osl_pktpush (osl_t * osh, void *skb, int bytes)
{
  return (skb_push ((struct sk_buff *) skb, bytes));
}

uchar *
osl_pktpull (osl_t * osh, void *skb, int bytes)
{
  return (skb_pull ((struct sk_buff *) skb, bytes));
}

void *
osl_pkttag (void *skb)
{
  return ((void *) (((struct sk_buff *) skb)->cb));
}

void *
osl_pktlink (void *skb)
{
  return (((struct sk_buff *) skb)->prev);
}

void
osl_pktsetlink (void *skb, void *x)
{
  ((struct sk_buff *) skb)->prev = (struct sk_buff *) x;
}

uint
osl_pktprio (void *skb)
{
  return (((struct sk_buff *) skb)->priority);
}

void
osl_pktsetprio (void *skb, uint x)
{
  ((struct sk_buff *) skb)->priority = x;
}

/* Convert a driver packet to native(OS) packet
 * In the process, packettag is zeroed out before sending up
 * IP code depends on skb->cb to be setup correctly with various options
 * In our case, that means it should be 0
 */
struct sk_buff *
osl_pkt_tonative (osl_t * osh, void *pkt)
{
  struct sk_buff *nskb;

  if (osh->pub.pkttag)
    bzero ((void *) ((struct sk_buff *) pkt)->cb, OSL_PKTTAG_SZ);

  /* Decrement the packet counter */
  for (nskb = (struct sk_buff *) pkt; nskb; nskb = nskb->next)
    {
#ifdef BCMDBG_PKT
      pktlist_remove (&(osh->pktlist), (void *) nskb);
#endif /* BCMDBG_PKT */
      osh->pub.pktalloced--;
    }

  return (struct sk_buff *) pkt;
}

/* Convert a native(OS) packet to driver packet.
 * In the process, native packet is destroyed, there is no copying
 * Also, a packettag is zeroed out
 */
void *
osl_pkt_frmnative (osl_t * osh, struct sk_buff *skb)
{
  struct sk_buff *nskb;

  if (osh->pub.pkttag)
    bzero ((void *) skb->cb, OSL_PKTTAG_SZ);

  /* Increment the packet counter */
  for (nskb = skb; nskb; nskb = nskb->next)
    {
#ifdef BCMDBG_PKT
      pktlist_add (&(osh->pktlist), (void *) nskb);
#endif /* BCMDBG_PKT */
      osh->pub.pktalloced++;
    }

  return (void *) skb;
}

#endif /* BINOSL */