/*
 * ---------------------------------------------------------------------------
 *  FILE:     os.c
 *
 *  PURPOSE:
 *      Routines to fulfil the OS-abstraction for the HIP lib.
 *      It is part of the porting exercise.
 *
 * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd.
 *
 * Refer to LICENSE.txt included with this source code for details on
 * the license terms.
 *
 * ---------------------------------------------------------------------------
 */

/**
 * The HIP lib OS abstraction consists of the implementation
 * of the functions in this file. It is part of the porting exercise.
 */

#include "unifi_priv.h"


/*
 * ---------------------------------------------------------------------------
 *  unifi_net_data_malloc
 *
 *      Allocate an OS specific net data buffer of "size" bytes.
 *      The bulk_data_slot.os_data_ptr must be initialised to point
 *      to the buffer allocated. The bulk_data_slot.length must be
 *      initialised to the requested size, zero otherwise.
 *      The bulk_data_slot.os_net_buf_ptr can be initialised to
 *      an OS specific pointer to be used in the unifi_net_data_free().
 *
 *
 *  Arguments:
 *      ospriv              Pointer to device private context struct.
 *      bulk_data_slot      Pointer to the bulk data structure to initialise.
 *      size                Size of the buffer to be allocated.
 *
 *  Returns:
 *      CSR_RESULT_SUCCESS on success, CSR_RESULT_FAILURE otherwise.
 * ---------------------------------------------------------------------------
 */
CsrResult
unifi_net_data_malloc(void *ospriv, bulk_data_desc_t *bulk_data_slot, unsigned int size)
{
    struct sk_buff *skb;
    unifi_priv_t *priv = (unifi_priv_t*)ospriv;
    int rounded_length;

    if (priv->card_info.sdio_block_size == 0) {
        unifi_error(priv, "unifi_net_data_malloc: Invalid SDIO block size\n");
        return CSR_RESULT_FAILURE;
    }

    rounded_length = (size + priv->card_info.sdio_block_size - 1) & ~(priv->card_info.sdio_block_size - 1);

    /*
     * (ETH_HLEN + 2) bytes tailroom for header manipulation
     * CSR_WIFI_ALIGN_BYTES bytes headroom for alignment manipulation
     */
    skb = dev_alloc_skb(rounded_length + 2 + ETH_HLEN + CSR_WIFI_ALIGN_BYTES);
    if (! skb) {
        unifi_error(ospriv, "alloc_skb failed.\n");
        bulk_data_slot->os_net_buf_ptr = NULL;
        bulk_data_slot->net_buf_length = 0;
        bulk_data_slot->os_data_ptr = NULL;
        bulk_data_slot->data_length = 0;
        return CSR_RESULT_FAILURE;
    }

    bulk_data_slot->os_net_buf_ptr = (const unsigned char*)skb;
    bulk_data_slot->net_buf_length = rounded_length + 2 + ETH_HLEN + CSR_WIFI_ALIGN_BYTES;
    bulk_data_slot->os_data_ptr = (const void*)skb->data;
    bulk_data_slot->data_length = size;

    return CSR_RESULT_SUCCESS;
} /* unifi_net_data_malloc() */

/*
 * ---------------------------------------------------------------------------
 *  unifi_net_data_free
 *
 *      Free an OS specific net data buffer.
 *      The bulk_data_slot.length must be initialised to 0.
 *
 *
 *  Arguments:
 *      ospriv              Pointer to device private context struct.
 *      bulk_data_slot      Pointer to the bulk data structure that
 *                          holds the data to be freed.
 *
 *  Returns:
 *      None.
 * ---------------------------------------------------------------------------
 */
void
unifi_net_data_free(void *ospriv, bulk_data_desc_t *bulk_data_slot)
{
    struct sk_buff *skb;
    CSR_UNUSED(ospriv);

    skb = (struct sk_buff *)bulk_data_slot->os_net_buf_ptr;
    dev_kfree_skb(skb);

    bulk_data_slot->net_buf_length = 0;
    bulk_data_slot->data_length = 0;
    bulk_data_slot->os_data_ptr = bulk_data_slot->os_net_buf_ptr = NULL;

} /* unifi_net_data_free() */


/*
* ---------------------------------------------------------------------------
*  unifi_net_dma_align
*
*      DMA align an OS specific net data buffer.
*      The buffer must be empty.
*
*
*  Arguments:
*      ospriv              Pointer to device private context struct.
*      bulk_data_slot      Pointer to the bulk data structure that
*                          holds the data to be aligned.
*
*  Returns:
*      None.
* ---------------------------------------------------------------------------
*/
CsrResult
unifi_net_dma_align(void *ospriv, bulk_data_desc_t *bulk_data_slot)
{
    struct sk_buff *skb;
    unsigned long buf_address;
    int offset;
    unifi_priv_t *priv = (unifi_priv_t*)ospriv;

    if ((bulk_data_slot == NULL) || (CSR_WIFI_ALIGN_BYTES == 0)) {
        return CSR_RESULT_SUCCESS;
    }

    if ((bulk_data_slot->os_data_ptr == NULL) || (bulk_data_slot->data_length == 0)) {
        return CSR_RESULT_SUCCESS;
    }

    buf_address = (unsigned long)(bulk_data_slot->os_data_ptr) & (CSR_WIFI_ALIGN_BYTES - 1);

    unifi_trace(priv, UDBG5,
                "unifi_net_dma_align: Allign buffer (0x%p) by %d bytes\n",
                bulk_data_slot->os_data_ptr, buf_address);

    offset = CSR_WIFI_ALIGN_BYTES - buf_address;
    if (offset < 0) {
        unifi_error(priv, "unifi_net_dma_align: Failed (offset=%d)\n", offset);
        return CSR_RESULT_FAILURE;
    }

    skb = (struct sk_buff*)(bulk_data_slot->os_net_buf_ptr);
    skb_reserve(skb, offset);
    bulk_data_slot->os_net_buf_ptr = (const unsigned char*)skb;
    bulk_data_slot->os_data_ptr = (const void*)(skb->data);

    return CSR_RESULT_SUCCESS;

} /* unifi_net_dma_align() */

#ifdef ANDROID_TIMESTAMP
static volatile unsigned int printk_cpu = UINT_MAX;
char tbuf[30];

char* print_time(void )
{
    unsigned long long t;
    unsigned long nanosec_rem;

    t = cpu_clock(printk_cpu);
    nanosec_rem = do_div(t, 1000000000);
    sprintf(tbuf, "[%5lu.%06lu] ",
                    (unsigned long) t,
                    nanosec_rem / 1000);

    return tbuf;
}
#endif


/* Module parameters */
extern int unifi_debug;

#ifdef UNIFI_DEBUG
#define DEBUG_BUFFER_SIZE       120

#define FORMAT_TRACE(_s, _len, _args, _fmt)             \
    do {                                                \
        va_start(_args, _fmt);                          \
        _len += vsnprintf(&(_s)[_len],                  \
                         (DEBUG_BUFFER_SIZE - _len),    \
                         _fmt, _args);                  \
        va_end(_args);                                  \
        if (_len >= DEBUG_BUFFER_SIZE) {                \
            (_s)[DEBUG_BUFFER_SIZE - 2] = '\n';         \
            (_s)[DEBUG_BUFFER_SIZE - 1] = 0;            \
        }                                               \
    } while (0)

void
unifi_error(void* ospriv, const char *fmt, ...)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;
    char s[DEBUG_BUFFER_SIZE];
    va_list args;
    unsigned int len;
#ifdef ANDROID_TIMESTAMP
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "%s unifi%d: ", print_time(), priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "%s unifi: ", print_time());
    }
#else
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "unifi%d: ", priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "unifi: ");
    }
#endif /* ANDROID_TIMESTAMP */
    FORMAT_TRACE(s, len, args, fmt);

    printk("%s", s);
}

void
unifi_warning(void* ospriv, const char *fmt, ...)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;
    char s[DEBUG_BUFFER_SIZE];
    va_list args;
    unsigned int len;

#ifdef ANDROID_TIMESTAMP
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_WARNING "%s unifi%d: ", print_time(), priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_WARNING "%s unifi: ", print_time());
    }
#else
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_WARNING "unifi%d: ", priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_WARNING "unifi: ");
    }
#endif /* ANDROID_TIMESTAMP */

    FORMAT_TRACE(s, len, args, fmt);

    printk("%s", s);
}


void
unifi_notice(void* ospriv, const char *fmt, ...)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;
    char s[DEBUG_BUFFER_SIZE];
    va_list args;
    unsigned int len;

#ifdef ANDROID_TIMESTAMP
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_NOTICE "%s unifi%d: ", print_time(), priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_NOTICE "%s unifi: ", print_time());
    }
#else
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_NOTICE "unifi%d: ", priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_NOTICE "unifi: ");
    }
#endif /* ANDROID_TIMESTAMP */

    FORMAT_TRACE(s, len, args, fmt);

    printk("%s", s);
}


void
unifi_info(void* ospriv, const char *fmt, ...)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;
    char s[DEBUG_BUFFER_SIZE];
    va_list args;
    unsigned int len;

#ifdef ANDROID_TIMESTAMP
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_INFO "%s unifi%d: ", print_time(), priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_INFO "%s unifi: ", print_time());
    }
#else
    if (priv != NULL) {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_INFO "unifi%d: ", priv->instance);
    } else {
        len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_INFO "unifi: ");
    }
#endif /* ANDROID_TIMESTAMP */

    FORMAT_TRACE(s, len, args, fmt);

    printk("%s", s);
}

/* debugging */
void
unifi_trace(void* ospriv, int level, const char *fmt, ...)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;
    char s[DEBUG_BUFFER_SIZE];
    va_list args;
    unsigned int len;

    if (unifi_debug >= level) {
#ifdef ANDROID_TIMESTAMP
        if (priv != NULL) {
            len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "%s unifi%d: ", print_time(), priv->instance);
        } else {
            len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "%s unifi: ", print_time());
        }
#else
        if (priv != NULL) {
            len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "unifi%d: ", priv->instance);
        } else {
            len = snprintf(s, DEBUG_BUFFER_SIZE, KERN_ERR "unifi: ");
        }
#endif /* ANDROID_TIMESTAMP */

        FORMAT_TRACE(s, len, args, fmt);

        printk("%s", s);
    }
}

#else

void
unifi_error_nop(void* ospriv, const char *fmt, ...)
{
}

void
unifi_trace_nop(void* ospriv, int level, const char *fmt, ...)
{
}

#endif /* UNIFI_DEBUG */


/*
 * ---------------------------------------------------------------------------
 *
 *      Debugging support.
 *
 * ---------------------------------------------------------------------------
 */

#ifdef UNIFI_DEBUG

/* Memory dump with level filter controlled by unifi_debug */
void
unifi_dump(void *ospriv, int level, const char *msg, void *mem, u16 len)
{
    unifi_priv_t *priv = (unifi_priv_t*) ospriv;

    if (unifi_debug >= level) {
#ifdef ANDROID_TIMESTAMP
        if (priv != NULL) {
            printk(KERN_ERR "%s unifi%d: --- dump: %s ---\n", print_time(), priv->instance, msg ? msg : "");
        } else {
            printk(KERN_ERR "%s unifi: --- dump: %s ---\n", print_time(), msg ? msg : "");
        }
#else
        if (priv != NULL) {
            printk(KERN_ERR "unifi%d: --- dump: %s ---\n", priv->instance, msg ? msg : "");
        } else {
            printk(KERN_ERR "unifi: --- dump: %s ---\n", msg ? msg : "");
        }
#endif /* ANDROID_TIMESTAMP */
        dump(mem, len);

        if (priv != NULL) {
            printk(KERN_ERR "unifi%d: --- end of dump ---\n", priv->instance);
        } else {
            printk(KERN_ERR "unifi: --- end of dump ---\n");
        }
    }
}

/* Memory dump that appears all the time, use sparingly */
void
dump(void *mem, u16 len)
{
    int i, col = 0;
    unsigned char *pdata = (unsigned char *)mem;
#ifdef ANDROID_TIMESTAMP
    printk("timestamp %s \n", print_time());
#endif /* ANDROID_TIMESTAMP */
    if (mem == NULL) {
        printk("(null dump)\n");
        return;
    }
    for (i = 0; i < len; i++) {
        if (col == 0)
            printk("0x%02X: ", i);

        printk(" %02X", pdata[i]);

        if (++col == 16) {
            printk("\n");
            col = 0;
        }
    }
    if (col)
        printk("\n");
} /* dump() */


void
dump16(void *mem, u16 len)
{
    int i, col=0;
    unsigned short *p = (unsigned short *)mem;
#ifdef ANDROID_TIMESTAMP
    printk("timestamp %s \n", print_time());
#endif /* ANDROID_TIMESTAMP */
    for (i = 0; i < len; i+=2) {
        if (col == 0)
            printk("0x%02X: ", i);

        printk(" %04X", *p++);

        if (++col == 8) {
            printk("\n");
            col = 0;
        }
    }
    if (col)
        printk("\n");
}


#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
void
dump_str(void *mem, u16 len)
{
    int i;
    unsigned char *pdata = (unsigned char *)mem;
#ifdef ANDROID_TIMESTAMP
    printk("timestamp %s \n", print_time());
#endif /* ANDROID_TIMESTAMP */
    for (i = 0; i < len; i++) {
        printk("%c", pdata[i]);
    }
	printk("\n");

} /* dump_str() */
#endif /* CSR_ONLY_NOTES */


#endif /* UNIFI_DEBUG */


/* ---------------------------------------------------------------------------
 *                              - End -
 * ------------------------------------------------------------------------- */