/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef _CNSS_DEBUG_H
#define _CNSS_DEBUG_H


#if 0
#include <linux/ipc_logging.h>
#endif
#include <linux/printk.h>


#ifdef CONFIG_CNSS2_IPC_LOGGING
#include <linux/types.h>

#define IPC_LOG_VERSION 0x0003
#define IPC_LOG_MAX_CONTEXT_NAME_LEN 32
#define MAX_MSG_SIZE 255

enum {
	TSV_TYPE_MSG_START = 1,
	TSV_TYPE_SKB = TSV_TYPE_MSG_START,
	TSV_TYPE_STRING,
	TSV_TYPE_MSG_END = TSV_TYPE_STRING,
};

struct tsv_header {
	unsigned char type;
	unsigned char size; /* size of data field */
};

struct encode_context {
	struct tsv_header hdr;
	char buff[MAX_MSG_SIZE];
	int offset;
};

struct decode_context {
	int output_format;      /* 0 = debugfs */
	char *buff;             /* output buffer */
	int size;               /* size of output buffer */
};

/*
 * ipc_log_context_create: Create a debug log context
 *                         Should not be called from atomic context
 *
 * @max_num_pages: Number of pages of logging space required (max. 10)
 * @mod_name     : Name of the directory entry under DEBUGFS
 * @feature_version : First 16 bit for version number of user-defined message
 *		      formats and next 16 bit for enabling minidump
 *
 * returns context id on success, NULL on failure
 */
void *ipc_log_context_create(int max_num_pages, const char *modname,
		uint32_t feature_version);

/*
 * msg_encode_start: Start encoding a log message
 *
 * @ectxt: Temporary storage to hold the encoded message
 * @type:  Root event type defined by the module which is logging
 */
void msg_encode_start(struct encode_context *ectxt, uint32_t type);

/*
 * tsv_timestamp_write: Writes the current timestamp count
 *
 * @ectxt: Context initialized by calling msg_encode_start()
 */
int tsv_timestamp_write(struct encode_context *ectxt);

/*
 * tsv_qtimer_write: Writes the current QTimer timestamp count
 *
 * @ectxt: Context initialized by calling msg_encode_start()
 */
int tsv_qtimer_write(struct encode_context *ectxt);

/*
 * tsv_pointer_write: Writes a data pointer
 *
 * @ectxt:   Context initialized by calling msg_encode_start()
 * @pointer: Pointer value to write
 */
int tsv_pointer_write(struct encode_context *ectxt, void *pointer);

/*
 * tsv_int32_write: Writes a 32-bit integer value
 *
 * @ectxt: Context initialized by calling msg_encode_start()
 * @n:     Integer to write
 */
int tsv_int32_write(struct encode_context *ectxt, int32_t n);

/*
 * tsv_byte_array_write: Writes a byte array
 *
 * @ectxt:     Context initialized by calling msg_encode_start()
 * @data:      Pointer to byte array
 * @data_size: Size of byte array
 */
int tsv_byte_array_write(struct encode_context *ectxt,
			 void *data, int data_size);

/*
 * msg_encode_end: Complete the message encode process
 *
 * @ectxt: Temporary storage which holds the encoded message
 */
void msg_encode_end(struct encode_context *ectxt);

/*
 * ipc_log_write: Commits message to logging ring buffer
 *
 * @ctxt:  Logging context
 * @ectxt: Temporary storage which holds the encoded message
 */
void ipc_log_write(void *ctxt, struct encode_context *ectxt);

/*
 * ipc_log_string: Helper function to log a string
 *
 * @ilctxt: Debug Log Context created using ipc_log_context_create()
 * @fmt:    Data specified using format specifiers
 */
int ipc_log_string(void *ilctxt, const char *fmt, ...) __printf(2, 3);

/**
 * ipc_log_extract - Reads and deserializes log
 *
 * @ilctxt:  logging context
 * @buff:    buffer to receive the data
 * @size:    size of the buffer
 * @returns: 0 if no data read; >0 number of bytes read; < 0 error
 *
 * If no data is available to be read, then the ilctxt::read_avail
 * completion is reinitialized.  This allows clients to block
 * until new log data is save.
 */
int ipc_log_extract(void *ilctxt, char *buff, int size);

/*
 * Print a string to decode context.
 * @dctxt   Decode context
 * @args   printf args
 */
#define IPC_SPRINTF_DECODE(dctxt, args...) \
do { \
	int i; \
	i = scnprintf(dctxt->buff, dctxt->size, args); \
	dctxt->buff += i; \
	dctxt->size -= i; \
} while (0)

/*
 * tsv_timestamp_read: Reads a timestamp
 *
 * @ectxt:  Context retrieved by reading from log space
 * @dctxt:  Temporary storage to hold the decoded message
 * @format: Output format while dumping through DEBUGFS
 */
void tsv_timestamp_read(struct encode_context *ectxt,
			struct decode_context *dctxt, const char *format);

/*
 * tsv_qtimer_read: Reads a QTimer timestamp
 *
 * @ectxt:  Context retrieved by reading from log space
 * @dctxt:  Temporary storage to hold the decoded message
 * @format: Output format while dumping through DEBUGFS
 */
void tsv_qtimer_read(struct encode_context *ectxt,
		     struct decode_context *dctxt, const char *format);

/*
 * tsv_pointer_read: Reads a data pointer
 *
 * @ectxt:  Context retrieved by reading from log space
 * @dctxt:  Temporary storage to hold the decoded message
 * @format: Output format while dumping through DEBUGFS
 */
void tsv_pointer_read(struct encode_context *ectxt,
		      struct decode_context *dctxt, const char *format);

/*
 * tsv_int32_read: Reads a 32-bit integer value
 *
 * @ectxt:  Context retrieved by reading from log space
 * @dctxt:  Temporary storage to hold the decoded message
 * @format: Output format while dumping through DEBUGFS
 */
int32_t tsv_int32_read(struct encode_context *ectxt,
		       struct decode_context *dctxt, const char *format);

/*
 * tsv_byte_array_read: Reads a byte array
 *
 * @ectxt:  Context retrieved by reading from log space
 * @dctxt:  Temporary storage to hold the decoded message
 * @format: Output format while dumping through DEBUGFS
 */
void tsv_byte_array_read(struct encode_context *ectxt,
			 struct decode_context *dctxt, const char *format);

/*
 * add_deserialization_func: Register a deserialization function to
 *                           to unpack the subevents of a main event
 *
 * @ctxt: Debug log context to which the deserialization function has
 *        to be registered
 * @type: Main/Root event, defined by the module which is logging, to
 *        which this deserialization function has to be registered.
 * @dfune: Deserialization function to be registered
 *
 * return 0 on success, -ve value on FAILURE
 */
int add_deserialization_func(void *ctxt, int type,
			void (*dfunc)(struct encode_context *,
				      struct decode_context *));

/*
 * ipc_log_context_destroy: Destroy debug log context
 *
 * @ctxt: debug log context created by calling ipc_log_context_create API.
 */
int ipc_log_context_destroy(void *ctxt);

/**
 * struct ipc_log_page_header - Individual log page header
 *
 * @magic: Magic number (used for log extraction)
 * @nmagic: Inverse of magic number (used for log extraction)
 * @page_num: Index of page (0.. N - 1) (note top bit is always set)
 * @read_offset:  Read offset in page
 * @write_offset: Write offset in page (or 0xFFFF if full)
 * @log_id: ID of logging context that owns this page
 * @start_time:  Scheduler clock for first write time in page
 * @end_time:  Scheduler clock for last write time in page
 * @ctx_offset:  Signed offset from page to the logging context.  Used to
 *               optimize ram-dump extraction.
 *
 * @list:  Linked list of pages that make up a log
 * @nd_read_offset:  Non-destructive read offset used for debugfs
 *
 * The first part of the structure defines data that is used to extract the
 * logs from a memory dump and elements in this section should not be changed
 * or re-ordered.  New local data structures can be added to the end of the
 * structure since they will be ignored by the extraction tool.
 */
struct ipc_log_page_header {
	uint32_t magic;
	uint32_t nmagic;
	uint32_t page_num;
	uint16_t read_offset;
	uint16_t write_offset;
	uint64_t log_id;
	uint64_t start_time;
	uint64_t end_time;
	int64_t ctx_offset;

	/* add local data structures after this point */
	struct list_head list;
	uint16_t nd_read_offset;
};

/**
 * struct ipc_log_page - Individual log page
 *
 * @hdr: Log page header
 * @data: Log data
 *
 * Each log consists of 1 to N log pages.  Data size is adjusted to always fit
 * the structure into a single kernel page.
 */
struct ipc_log_page {
	struct ipc_log_page_header hdr;
	char data[PAGE_SIZE - sizeof(struct ipc_log_page_header)];
};

/**
 * struct ipc_log_context - main logging context
 *
 * @magic:  Magic number (used for log extraction)
 * @nmagic:  Inverse of magic number (used for log extraction)
 * @version:  IPC Logging version of log format
 * @user_version:  Version number for user-defined messages
 * @header_size:  Size of the log header which is used to determine the offset
 *                of ipc_log_page::data
 * @log_id:  Log ID (assigned when log is created)
 * @name:  Name of the log used to uniquely identify the log during extraction
 *
 * @list:  List of log contexts (struct ipc_log_context)
 * @page_list:  List of log pages (struct ipc_log_page)
 * @first_page:  First page in list of logging pages
 * @last_page:  Last page in list of logging pages
 * @write_page:  Current write page
 * @read_page:  Current read page (for internal reads)
 * @nd_read_page:  Current debugfs extraction page (non-destructive)
 *
 * @write_avail:  Number of bytes available to write in all pages
 * @dent:  Debugfs node for run-time log extraction
 * @dfunc_info_list:  List of deserialization functions
 * @context_lock_lhb1:  Lock for entire structure
 * @read_avail:  Completed when new data is added to the log
 */
struct ipc_log_context {
	uint32_t magic;
	uint32_t nmagic;
	uint32_t version;
	uint16_t user_version;
	uint16_t header_size;
	uint64_t log_id;
	char name[IPC_LOG_MAX_CONTEXT_NAME_LEN];

	/* add local data structures after this point */
	struct list_head list;
	struct list_head page_list;
	struct ipc_log_page *first_page;
	struct ipc_log_page *last_page;
	struct ipc_log_page *write_page;
	struct ipc_log_page *read_page;
	struct ipc_log_page *nd_read_page;

	uint32_t write_avail;
	struct dentry *dent;
	struct list_head dfunc_info_list;
	spinlock_t context_lock_lhb1;
	struct completion read_avail;
	struct kref refcount;
	bool destroyed;
};

struct dfunc_info {
	struct list_head list;
	int type;
	void (*dfunc)(struct encode_context *enc, struct decode_context *dec);
};

enum {
	TSV_TYPE_INVALID,
	TSV_TYPE_TIMESTAMP,
	TSV_TYPE_POINTER,
	TSV_TYPE_INT32,
	TSV_TYPE_BYTE_ARRAY,
	TSV_TYPE_QTIMER,
};

enum {
	OUTPUT_DEBUGFS,
};

#define IPC_LOG_CONTEXT_MAGIC_NUM 0x25874452
#define IPC_LOGGING_MAGIC_NUM 0x52784425
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define IS_MSG_TYPE(x) (((x) > TSV_TYPE_MSG_START) && \
			((x) < TSV_TYPE_MSG_END))
#define MAX_MSG_DECODED_SIZE (MAX_MSG_SIZE*4)

void ipc_log_context_free(struct kref *kref);

static inline void ipc_log_context_put(struct ipc_log_context *ilctxt)
{
	kref_put(&ilctxt->refcount, ipc_log_context_free);
}

#if (defined(CONFIG_DEBUG_FS))
void check_and_create_debugfs(void);

void create_ctx_debugfs(struct ipc_log_context *ctxt,
			const char *mod_name);
#else
void check_and_create_debugfs(void)
{
}

void create_ctx_debugfs(struct ipc_log_context *ctxt, const char *mod_name)
{
}
#endif
#endif

#define CNSS_IPC_LOG_PAGES		32
#define RDDM_DONE_DELAY        100  /* in msecs */

enum cnss_log_level {
	CNSS_LOG_LEVEL_NONE,
	CNSS_LOG_LEVEL_ERROR,
	CNSS_LOG_LEVEL_WARN,
	CNSS_LOG_LEVEL_INFO,
	CNSS_LOG_LEVEL_DEBUG,
	CNSS_LOG_LEVEL_MAX
};

extern int log_level;
extern int rddm_done_timeout;


#ifdef CONFIG_CNSS2_IPC_LOGGING
extern void *cnss_ipc_log_context;
extern void *cnss_ipc_log_long_context;

#define cnss_ipc_log_string(_x...) do {					\
		if (cnss_ipc_log_context)				\
			ipc_log_string(cnss_ipc_log_context, _x);	\
	} while (0)

#define cnss_ipc_log_long_string(_x...) do {				\
		if (cnss_ipc_log_long_context)				\
			ipc_log_string(cnss_ipc_log_long_context, _x);	\
	} while (0)
#else
#define cnss_ipc_log_string(_x...) do {					\
	} while (0)

#define cnss_ipc_log_long_string(_x...) do {				\
	} while (0)
#endif

#define cnss_pr_err(_fmt, ...) do {					\
		if (plat_priv) {					\
			pr_err("cnss[%x]: ERR: " _fmt,			\
			       plat_priv->wlfw_service_instance_id,	\
			       ##__VA_ARGS__);				\
			cnss_ipc_log_string("[%x] ERR: " pr_fmt(_fmt),	\
					    plat_priv->			\
					    wlfw_service_instance_id,	\
					    ##__VA_ARGS__);		\
		} else {						\
			pr_err("cnss: ERR: " _fmt, ##__VA_ARGS__);	\
		}							\
	} while (0)

#define cnss_pr_warn(_fmt, ...) do {					\
		if (plat_priv) {					\
			if (log_level >= CNSS_LOG_LEVEL_WARN)		\
				pr_err("cnss[%x]: WARN: " _fmt,		\
				       plat_priv->			\
				       wlfw_service_instance_id,	\
				       ##__VA_ARGS__);			\
			else						\
				pr_warn("cnss[%x]: WARN: " _fmt,	\
					plat_priv->			\
					wlfw_service_instance_id,	\
					##__VA_ARGS__);			\
			cnss_ipc_log_string("[%x] WRN: " pr_fmt(_fmt),	\
					    plat_priv->			\
					    wlfw_service_instance_id,	\
					    ##__VA_ARGS__);		\
		} else {						\
			pr_err("cnss: WARN: " _fmt, ##__VA_ARGS__);	\
		}							\
	} while (0)

#define cnss_pr_info(_fmt, ...) do {					\
		if (plat_priv) {					\
			if (log_level >= CNSS_LOG_LEVEL_INFO)		\
				pr_err("cnss[%x]: INFO: " _fmt,		\
				       plat_priv->			\
				       wlfw_service_instance_id,	\
				       ##__VA_ARGS__);			\
			else						\
				pr_info("cnss[%x]: INFO: " _fmt,	\
					plat_priv->			\
					wlfw_service_instance_id,	\
					##__VA_ARGS__);			\
			cnss_ipc_log_string("[%x] INF: " pr_fmt(_fmt),	\
					    plat_priv->			\
					    wlfw_service_instance_id,	\
					    ##__VA_ARGS__);		\
		} else {						\
			pr_err("cnss: INFO: " _fmt, ##__VA_ARGS__);	\
		}							\
	} while (0)

#define cnss_pr_dbg(_fmt, ...) do {					\
		if (plat_priv) {					\
			if (log_level >= CNSS_LOG_LEVEL_DEBUG)		\
				pr_err("cnss[%x]: DBG: " _fmt,		\
				       plat_priv->			\
				       wlfw_service_instance_id,	\
				       ##__VA_ARGS__);			\
			else						\
				pr_debug("cnss[%x]: DBG: " _fmt,	\
					 plat_priv->			\
					 wlfw_service_instance_id,	\
					 ##__VA_ARGS__);		\
			cnss_ipc_log_string("[%x] DBG: " pr_fmt(_fmt),	\
					    plat_priv->			\
					    wlfw_service_instance_id,	\
					    ##__VA_ARGS__);		\
		} else {						\
			if (log_level >= CNSS_LOG_LEVEL_DEBUG)          \
				pr_err("cnss: DBG: " _fmt,              \
				       ##__VA_ARGS__);                  \
		}							\
	} while (0)

#define cnss_pr_vdbg(_fmt, ...) do {					\
		pr_err("cnss: " _fmt, ##__VA_ARGS__);	\
		cnss_ipc_log_long_string("%scnss: " _fmt, "",		\
					 ##__VA_ARGS__);		\
	} while (0)

#define CNSS_ASSERT(_condition) do {					\
		if (!(_condition) &&					\
		    cnss_wait_for_rddm_complete(plat_priv)) {		\
			cnss_dump_qmi_history();			\
			cnss_pr_err("ASSERT at line %d\n",		\
				    __LINE__);				\
			BUG_ON(1);					\
		}							\
	} while (0)

#define cnss_fatal_err(_fmt, ...) do {					\
		if (plat_priv) {					\
			pr_err("cnss[%x]: FATAL: " _fmt,		\
			       plat_priv->wlfw_service_instance_id,	\
			       ##__VA_ARGS__);				\
		} else {						\
			pr_err("cnss: FATAL: " _fmt, ##__VA_ARGS__);	\
		}							\
	} while (0)

bool cnss_wait_for_rddm_complete(struct cnss_plat_data *plat_priv);

#ifdef CONFIG_CNSS2_IPC_LOGGING
int cnss_debug_init(void);
void cnss_debug_deinit(void);
#else
static inline int cnss_debug_init(void)
{
	return 0;
}
static inline void cnss_debug_deinit(void)
{
}
#endif
int cnss_debugfs_create(struct cnss_plat_data *plat_priv);
void cnss_debugfs_destroy(struct cnss_plat_data *plat_priv);
void qmi_record(u8 instance_id, u16 msg_id, s8 error_msg, s8 resp_err_msg);

#endif /* _CNSS_DEBUG_H */