| /* | |
| * Copyright 2012-15 Advanced Micro Devices, Inc. | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the "Software"), | |
| * to deal in the Software without restriction, including without limitation | |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| * and/or sell copies of the Software, and to permit persons to whom the | |
| * Software is furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
| * OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * Authors: AMD | |
| * | |
| */ | |
| #include "dm_services.h" | |
| #include "include/logger_interface.h" | |
| #include "logger.h" | |
| #define NUM_ELEMENTS(a) (sizeof(a) / sizeof((a)[0])) | |
| static const struct dc_log_type_info log_type_info_tbl[] = { | |
| {LOG_ERROR, "Error"}, | |
| {LOG_WARNING, "Warning"}, | |
| {LOG_DEBUG, "Debug"}, | |
| {LOG_DC, "DC_Interface"}, | |
| {LOG_SURFACE, "Surface"}, | |
| {LOG_HW_HOTPLUG, "HW_Hotplug"}, | |
| {LOG_HW_LINK_TRAINING, "HW_LKTN"}, | |
| {LOG_HW_SET_MODE, "HW_Mode"}, | |
| {LOG_HW_RESUME_S3, "HW_Resume"}, | |
| {LOG_HW_AUDIO, "HW_Audio"}, | |
| {LOG_HW_HPD_IRQ, "HW_HPDIRQ"}, | |
| {LOG_MST, "MST"}, | |
| {LOG_SCALER, "Scaler"}, | |
| {LOG_BIOS, "BIOS"}, | |
| {LOG_BANDWIDTH_CALCS, "BWCalcs"}, | |
| {LOG_BANDWIDTH_VALIDATION, "BWValidation"}, | |
| {LOG_I2C_AUX, "I2C_AUX"}, | |
| {LOG_SYNC, "Sync"}, | |
| {LOG_BACKLIGHT, "Backlight"}, | |
| {LOG_FEATURE_OVERRIDE, "Override"}, | |
| {LOG_DETECTION_EDID_PARSER, "Edid"}, | |
| {LOG_DETECTION_DP_CAPS, "DP_Caps"}, | |
| {LOG_RESOURCE, "Resource"}, | |
| {LOG_DML, "DML"}, | |
| {LOG_EVENT_MODE_SET, "Mode"}, | |
| {LOG_EVENT_DETECTION, "Detect"}, | |
| {LOG_EVENT_LINK_TRAINING, "LKTN"}, | |
| {LOG_EVENT_LINK_LOSS, "LinkLoss"}, | |
| {LOG_EVENT_UNDERFLOW, "Underflow"}, | |
| {LOG_IF_TRACE, "InterfaceTrace"}, | |
| {LOG_DTN, "DTN"}, | |
| {LOG_DISPLAYSTATS, "DisplayStats"} | |
| }; | |
| /* ----------- Object init and destruction ----------- */ | |
| static bool construct(struct dc_context *ctx, struct dal_logger *logger, | |
| uint32_t log_mask) | |
| { | |
| /* malloc buffer and init offsets */ | |
| logger->log_buffer_size = DAL_LOGGER_BUFFER_MAX_SIZE; | |
| logger->log_buffer = kcalloc(logger->log_buffer_size, sizeof(char), | |
| GFP_KERNEL); | |
| if (!logger->log_buffer) | |
| return false; | |
| /* Initialize both offsets to start of buffer (empty) */ | |
| logger->buffer_read_offset = 0; | |
| logger->buffer_write_offset = 0; | |
| logger->open_count = 0; | |
| logger->flags.bits.ENABLE_CONSOLE = 1; | |
| logger->flags.bits.ENABLE_BUFFER = 0; | |
| logger->ctx = ctx; | |
| logger->mask = log_mask; | |
| return true; | |
| } | |
| static void destruct(struct dal_logger *logger) | |
| { | |
| if (logger->log_buffer) { | |
| kfree(logger->log_buffer); | |
| logger->log_buffer = NULL; | |
| } | |
| } | |
| struct dal_logger *dal_logger_create(struct dc_context *ctx, uint32_t log_mask) | |
| { | |
| /* malloc struct */ | |
| struct dal_logger *logger = kzalloc(sizeof(struct dal_logger), | |
| GFP_KERNEL); | |
| if (!logger) | |
| return NULL; | |
| if (!construct(ctx, logger, log_mask)) { | |
| kfree(logger); | |
| return NULL; | |
| } | |
| return logger; | |
| } | |
| uint32_t dal_logger_destroy(struct dal_logger **logger) | |
| { | |
| if (logger == NULL || *logger == NULL) | |
| return 1; | |
| destruct(*logger); | |
| kfree(*logger); | |
| *logger = NULL; | |
| return 0; | |
| } | |
| /* ------------------------------------------------------------------------ */ | |
| static bool dal_logger_should_log( | |
| struct dal_logger *logger, | |
| enum dc_log_type log_type) | |
| { | |
| if (logger->mask & (1 << log_type)) | |
| return true; | |
| return false; | |
| } | |
| static void log_to_debug_console(struct log_entry *entry) | |
| { | |
| struct dal_logger *logger = entry->logger; | |
| if (logger->flags.bits.ENABLE_CONSOLE == 0) | |
| return; | |
| if (entry->buf_offset) { | |
| switch (entry->type) { | |
| case LOG_ERROR: | |
| dm_error("%s", entry->buf); | |
| break; | |
| default: | |
| dm_output_to_console("%s", entry->buf); | |
| break; | |
| } | |
| } | |
| } | |
| /* Print everything unread existing in log_buffer to debug console*/ | |
| void dm_logger_flush_buffer(struct dal_logger *logger, bool should_warn) | |
| { | |
| char *string_start = &logger->log_buffer[logger->buffer_read_offset]; | |
| if (should_warn) | |
| dm_output_to_console( | |
| "---------------- FLUSHING LOG BUFFER ----------------\n"); | |
| while (logger->buffer_read_offset < logger->buffer_write_offset) { | |
| if (logger->log_buffer[logger->buffer_read_offset] == '\0') { | |
| dm_output_to_console("%s", string_start); | |
| string_start = logger->log_buffer + logger->buffer_read_offset + 1; | |
| } | |
| logger->buffer_read_offset++; | |
| } | |
| if (should_warn) | |
| dm_output_to_console( | |
| "-------------- END FLUSHING LOG BUFFER --------------\n\n"); | |
| } | |
| static void log_to_internal_buffer(struct log_entry *entry) | |
| { | |
| uint32_t size = entry->buf_offset; | |
| struct dal_logger *logger = entry->logger; | |
| if (logger->flags.bits.ENABLE_BUFFER == 0) | |
| return; | |
| if (logger->log_buffer == NULL) | |
| return; | |
| if (size > 0 && size < logger->log_buffer_size) { | |
| int buffer_space = logger->log_buffer_size - | |
| logger->buffer_write_offset; | |
| if (logger->buffer_write_offset == logger->buffer_read_offset) { | |
| /* Buffer is empty, start writing at beginning */ | |
| buffer_space = logger->log_buffer_size; | |
| logger->buffer_write_offset = 0; | |
| logger->buffer_read_offset = 0; | |
| } | |
| if (buffer_space > size) { | |
| /* No wrap around, copy 'size' bytes | |
| * from 'entry->buf' to 'log_buffer' | |
| */ | |
| memmove(logger->log_buffer + | |
| logger->buffer_write_offset, | |
| entry->buf, size); | |
| logger->buffer_write_offset += size; | |
| } else { | |
| /* Not enough room remaining, we should flush | |
| * existing logs */ | |
| /* Flush existing unread logs to console */ | |
| dm_logger_flush_buffer(logger, true); | |
| /* Start writing to beginning of buffer */ | |
| memmove(logger->log_buffer, entry->buf, size); | |
| logger->buffer_write_offset = size; | |
| logger->buffer_read_offset = 0; | |
| } | |
| } | |
| } | |
| static void log_heading(struct log_entry *entry) | |
| { | |
| int j; | |
| for (j = 0; j < NUM_ELEMENTS(log_type_info_tbl); j++) { | |
| const struct dc_log_type_info *info = &log_type_info_tbl[j]; | |
| if (info->type == entry->type) | |
| dm_logger_append(entry, "[%s]\t", info->name); | |
| } | |
| } | |
| static void append_entry( | |
| struct log_entry *entry, | |
| char *buffer, | |
| uint32_t buf_size) | |
| { | |
| if (!entry->buf || | |
| entry->buf_offset + buf_size > entry->max_buf_bytes | |
| ) { | |
| BREAK_TO_DEBUGGER(); | |
| return; | |
| } | |
| /* Todo: check if off by 1 byte due to \0 anywhere */ | |
| memmove(entry->buf + entry->buf_offset, buffer, buf_size); | |
| entry->buf_offset += buf_size; | |
| } | |
| /* ------------------------------------------------------------------------ */ | |
| /* Warning: Be careful that 'msg' is null terminated and the total size is | |
| * less than DAL_LOGGER_BUFFER_MAX_LOG_LINE_SIZE (256) including '\0' | |
| */ | |
| void dm_logger_write( | |
| struct dal_logger *logger, | |
| enum dc_log_type log_type, | |
| const char *msg, | |
| ...) | |
| { | |
| if (logger && dal_logger_should_log(logger, log_type)) { | |
| uint32_t size; | |
| va_list args; | |
| char buffer[LOG_MAX_LINE_SIZE]; | |
| struct log_entry entry; | |
| va_start(args, msg); | |
| entry.logger = logger; | |
| entry.buf = buffer; | |
| entry.buf_offset = 0; | |
| entry.max_buf_bytes = DAL_LOGGER_BUFFER_MAX_SIZE * sizeof(char); | |
| entry.type = log_type; | |
| log_heading(&entry); | |
| size = dm_log_to_buffer( | |
| buffer, LOG_MAX_LINE_SIZE - 1, msg, args); | |
| buffer[entry.buf_offset + size] = '\0'; | |
| entry.buf_offset += size + 1; | |
| /* --Flush log_entry buffer-- */ | |
| /* print to kernel console */ | |
| log_to_debug_console(&entry); | |
| /* log internally for dsat */ | |
| log_to_internal_buffer(&entry); | |
| va_end(args); | |
| } | |
| } | |
| /* Same as dm_logger_write, except without open() and close(), which must | |
| * be done separately. | |
| */ | |
| void dm_logger_append( | |
| struct log_entry *entry, | |
| const char *msg, | |
| ...) | |
| { | |
| va_list args; | |
| va_start(args, msg); | |
| dm_logger_append_va(entry, msg, args); | |
| va_end(args); | |
| } | |
| void dm_logger_append_va( | |
| struct log_entry *entry, | |
| const char *msg, | |
| va_list args) | |
| { | |
| struct dal_logger *logger; | |
| if (!entry) { | |
| BREAK_TO_DEBUGGER(); | |
| return; | |
| } | |
| logger = entry->logger; | |
| if (logger && logger->open_count > 0 && | |
| dal_logger_should_log(logger, entry->type)) { | |
| uint32_t size; | |
| char buffer[LOG_MAX_LINE_SIZE]; | |
| size = dm_log_to_buffer( | |
| buffer, LOG_MAX_LINE_SIZE, msg, args); | |
| if (size < LOG_MAX_LINE_SIZE - 1) { | |
| append_entry(entry, buffer, size); | |
| } else { | |
| append_entry(entry, "LOG_ERROR, line too long\n", 27); | |
| } | |
| } | |
| } | |
| void dm_logger_open( | |
| struct dal_logger *logger, | |
| struct log_entry *entry, /* out */ | |
| enum dc_log_type log_type) | |
| { | |
| if (!entry) { | |
| BREAK_TO_DEBUGGER(); | |
| return; | |
| } | |
| entry->type = log_type; | |
| entry->logger = logger; | |
| entry->buf = kzalloc(DAL_LOGGER_BUFFER_MAX_SIZE, | |
| GFP_KERNEL); | |
| entry->buf_offset = 0; | |
| entry->max_buf_bytes = DAL_LOGGER_BUFFER_MAX_SIZE * sizeof(char); | |
| logger->open_count++; | |
| log_heading(entry); | |
| } | |
| void dm_logger_close(struct log_entry *entry) | |
| { | |
| struct dal_logger *logger = entry->logger; | |
| if (logger && logger->open_count > 0) { | |
| logger->open_count--; | |
| } else { | |
| BREAK_TO_DEBUGGER(); | |
| goto cleanup; | |
| } | |
| /* --Flush log_entry buffer-- */ | |
| /* print to kernel console */ | |
| log_to_debug_console(entry); | |
| /* log internally for dsat */ | |
| log_to_internal_buffer(entry); | |
| /* TODO: Write end heading */ | |
| cleanup: | |
| if (entry->buf) { | |
| kfree(entry->buf); | |
| entry->buf = NULL; | |
| entry->buf_offset = 0; | |
| entry->max_buf_bytes = 0; | |
| } | |
| } | |