/* 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 */