#define ALL_AREAS 0 /* copy all debug areas */ #define NO_AREAS 1 /* copy no debug areas */
/* typedefs */
typedefstruct file_private_info {
loff_t offset; /* offset of last read in file */ int act_area; /* number of last formatted area */ int act_page; /* act page in given area */ int act_entry; /* last formatted entry (offset */ /* relative to beginning of last */ /* formatted page) */
size_t act_entry_offset; /* up to this offset we copied */ /* in last read the last formatted */ /* entry to userland */ char temp_buf[2048]; /* buffer for output */
debug_info_t *debug_info_org; /* original debug information */
debug_info_t *debug_info_snap; /* snapshot of debug information */ struct debug_view *view; /* used view of debug info */
} file_private_info_t;
typedefstruct { char *string; /* * This assumes that all args are converted into longs * on L/390 this is the case for all types of parameter * except of floats, and long long (32 bit) *
*/ long args[];
} debug_sprintf_entry_t;
/* * debug_areas_alloc * - Debug areas are implemented as a threedimensonal array: * areas[areanumber][pagenumber][pageoffset]
*/
static debug_entry_t ***debug_areas_alloc(int pages_per_area, int nr_areas)
{
debug_entry_t ***areas; int i, j;
areas = kmalloc_array(nr_areas, sizeof(debug_entry_t **), GFP_KERNEL); if (!areas) goto fail_malloc_areas; for (i = 0; i < nr_areas; i++) { /* GFP_NOWARN to avoid user triggerable WARN, we handle fails */
areas[i] = kmalloc_array(pages_per_area, sizeof(debug_entry_t *),
GFP_KERNEL | __GFP_NOWARN); if (!areas[i]) goto fail_malloc_areas2; for (j = 0; j < pages_per_area; j++) {
areas[i][j] = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!areas[i][j]) { for (j--; j >= 0 ; j--)
kfree(areas[i][j]);
kfree(areas[i]); goto fail_malloc_areas2;
}
}
} return areas;
fail_malloc_areas2: for (i--; i >= 0; i--) { for (j = 0; j < pages_per_area; j++)
kfree(areas[i][j]);
kfree(areas[i]);
}
kfree(areas);
fail_malloc_areas: return NULL;
}
/* * debug_info_alloc * - alloc new debug-info
*/ static debug_info_t *debug_info_alloc(constchar *name, int pages_per_area, int nr_areas, int buf_size, int level, int mode)
{
debug_info_t *rc;
/* * debug_info_copy * - copy debug-info
*/ static debug_info_t *debug_info_copy(debug_info_t *in, int mode)
{ unsignedlong flags;
debug_info_t *rc; int i, j;
/* get a consistent copy of the debug areas */ do {
rc = debug_info_alloc(in->name, in->pages_per_area,
in->nr_areas, in->buf_size, in->level, mode);
spin_lock_irqsave(&in->lock, flags); if (!rc) goto out; /* has something changed in the meantime ? */ if ((rc->pages_per_area == in->pages_per_area) &&
(rc->nr_areas == in->nr_areas)) { break;
}
spin_unlock_irqrestore(&in->lock, flags);
debug_info_free(rc);
} while (1);
if (mode == NO_AREAS) goto out;
for (i = 0; i < in->nr_areas; i++) { for (j = 0; j < in->pages_per_area; j++)
memcpy(rc->areas[i][j], in->areas[i][j], PAGE_SIZE);
rc->active_pages[i] = in->active_pages[i];
rc->active_entries[i] = in->active_entries[i];
}
rc->active_area = in->active_area;
out:
spin_unlock_irqrestore(&in->lock, flags); return rc;
}
/* * debug_info_get * - increments reference count for debug-info
*/ staticvoid debug_info_get(debug_info_t *db_info)
{ if (db_info)
refcount_inc(&db_info->ref_count);
}
/* * debug_info_put: * - decreases reference count for debug-info and frees it if necessary
*/ staticvoid debug_info_put(debug_info_t *db_info)
{ if (!db_info) return; if (refcount_dec_and_test(&db_info->ref_count))
debug_info_free(db_info);
}
/* * debug_format_entry: * - format one debug entry and return size of formatted data
*/ staticint debug_format_entry(file_private_info_t *p_info)
{
debug_info_t *id_snap = p_info->debug_info_snap; struct debug_view *view = p_info->view;
debug_entry_t *act_entry;
size_t len = 0;
if (p_info->act_entry == DEBUG_PROLOG_ENTRY) { /* print prolog */ if (view->prolog_proc) {
len += view->prolog_proc(id_snap, view, p_info->temp_buf, sizeof(p_info->temp_buf));
} goto out;
} if (!id_snap->areas) /* this is true, if we have a prolog only view */ goto out; /* or if 'pages_per_area' is 0 */
act_entry = (debug_entry_t *) ((char *)id_snap->areas[p_info->act_area]
[p_info->act_page] + p_info->act_entry);
if (act_entry->clock == 0LL) goto out; /* empty entry */ if (view->header_proc) {
len += view->header_proc(id_snap, view, p_info->act_area,
act_entry, p_info->temp_buf + len, sizeof(p_info->temp_buf) - len);
} if (view->format_proc) {
len += view->format_proc(id_snap, view, p_info->temp_buf + len, sizeof(p_info->temp_buf) - len,
DEBUG_DATA(act_entry));
}
out: return len;
}
/** * debug_next_entry - Go to the next entry * @p_info: Private info that is manipulated * * Sets the current position in @p_info to the next entry. If no further entry * exists the current position is set to one after the end the return value * indicates that no further entries exist. * * Return: True if there are more following entries, false otherwise
*/ staticinlinebool debug_next_entry(file_private_info_t *p_info)
{
debug_info_t *id;
id = p_info->debug_info_snap; if (p_info->act_entry == DEBUG_PROLOG_ENTRY) {
p_info->act_entry = 0;
p_info->act_page = 0; returntrue;
} if (!id->areas) returnfalse;
p_info->act_entry += id->entry_size; /* switch to next page, if we reached the end of the page */ if (p_info->act_entry > (PAGE_SIZE - id->entry_size)) { /* next page */
p_info->act_entry = 0;
p_info->act_page += 1; if ((p_info->act_page % id->pages_per_area) == 0) { /* next area */
p_info->act_area++;
p_info->act_page = 0;
} if (p_info->act_area >= id->nr_areas) returnfalse;
} returntrue;
}
/** * debug_to_act_entry - Go to the currently active entry * @p_info: Private info that is manipulated * * Sets the current position in @p_info to the currently active * entry of @p_info->debug_info_snap
*/ staticvoid debug_to_act_entry(file_private_info_t *p_info)
{
debug_info_t *snap_id;
/** * debug_prev_entry - Go to the previous entry * @p_info: Private info that is manipulated * * Sets the current position in @p_info to the previous entry. If no previous entry * exists the current position is set left as DEBUG_PROLOG_ENTRY and the return value * indicates that no previous entries exist. * * Return: True if there are more previous entries, false otherwise
*/
id = p_info->debug_info_snap; if (p_info->act_entry == DEBUG_PROLOG_ENTRY)
debug_to_act_entry(p_info); if (!id->areas) returnfalse;
p_info->act_entry -= id->entry_size; /* switch to prev page, if we reached the beginning of the page */ if (p_info->act_entry < 0) { /* end of previous page */
p_info->act_entry = rounddown(PAGE_SIZE, id->entry_size) - id->entry_size;
p_info->act_page--; if (p_info->act_page < 0) { /* previous area */
p_info->act_area--;
p_info->act_page = id->pages_per_area - 1;
} if (p_info->act_area < 0)
p_info->act_area = (id->nr_areas - 1) % id->nr_areas;
} /* check full circle */ if (id->active_area == p_info->act_area &&
id->active_pages[id->active_area] == p_info->act_page &&
id->active_entries[id->active_area] == p_info->act_entry) returnfalse; returntrue;
}
/** * debug_move_entry - Go to next entry in either the forward or backward direction * @p_info: Private info that is manipulated * @reverse: If true go to the next entry in reverse i.e. previous * * Sets the current position in @p_info to the next (@reverse == false) or * previous (@reverse == true) entry. * * Return: True if there are further entries in that direction, * false otherwise.
*/ staticbool debug_move_entry(file_private_info_t *p_info, bool reverse)
{ if (reverse) return debug_prev_entry(p_info); else return debug_next_entry(p_info);
}
/* * debug_output: * - called for user read() * - copies formatted debug entries to the user buffer
*/ static ssize_t debug_output(struct file *file, /* file descriptor */ char __user *user_buf, /* user buffer */
size_t len, /* length of buffer */
loff_t *offset) /* offset in the file */
{
size_t count = 0;
size_t entry_offset;
file_private_info_t *p_info;
p_info = (file_private_info_t *) file->private_data; if (*offset != p_info->offset) return -EPIPE; if (p_info->act_area >= p_info->debug_info_snap->nr_areas) return 0;
entry_offset = p_info->act_entry_offset; while (count < len) { int formatted_line_residue; int formatted_line_size; int user_buf_residue;
size_t copy_size;
/* * Make snapshot of current debug areas to get it consistent. * To copy all the areas is only needed, if we have a view which * formats the debug areas.
*/ if (!view->format_proc && !view->header_proc)
debug_info_snapshot = debug_info_copy(debug_info, NO_AREAS); else
debug_info_snapshot = debug_info_copy(debug_info, ALL_AREAS);
/* * debug_open: * - called for user open() * - copies formatted output to private_data area of the file * handle
*/ staticint debug_open(struct inode *inode, struct file *file)
{
debug_info_t *debug_info;
file_private_info_t *p_info; int i, rc = 0;
mutex_lock(&debug_mutex);
debug_info = file_inode(file)->i_private; /* find debug view */ for (i = 0; i < DEBUG_MAX_VIEWS; i++) { if (!debug_info->views[i]) continue; elseif (debug_info->debugfs_entries[i] == file->f_path.dentry) goto found; /* found view ! */
} /* no entry found */
rc = -EINVAL; goto out;
staticvoid debug_file_private_free(file_private_info_t *p_info)
{ if (p_info->debug_info_snap)
debug_info_free(p_info->debug_info_snap);
debug_info_put(p_info->debug_info_org);
kfree(p_info);
}
/* * debug_close: * - called for user close() * - deletes private_data area of the file handle
*/ staticint debug_close(struct inode *inode, struct file *file)
{
file_private_info_t *p_info;
/** * debug_dump - Get a textual representation of debug info, or as much as fits * @id: Debug information to use * @view: View with which to dump the debug information * @buf: Buffer the textual debug data representation is written to * @buf_size: Size of the buffer, including the trailing '\0' byte * @reverse: Go backwards from the last written entry * * This function may be used whenever a textual representation of the debug * information is required without using an s390dbf file. * * Note: It is the callers responsibility to supply a view that is compatible * with the debug information data. * * Return: On success returns the number of bytes written to the buffer not * including the trailing '\0' byte. If bug_size == 0 the function returns 0. * On failure an error code less than 0 is returned.
*/
ssize_t debug_dump(debug_info_t *id, struct debug_view *view, char *buf, size_t buf_size, bool reverse)
{
file_private_info_t *p_info;
size_t size, offset = 0;
/* Need space for '\0' byte */ if (buf_size < 1) return 0;
buf_size--;
p_info = debug_file_private_alloc(id, view); if (!p_info) return -ENOMEM;
/* There is always at least the DEBUG_PROLOG_ENTRY */ do {
size = debug_format_entry(p_info);
size = min(size, buf_size - offset);
memcpy(buf + offset, p_info->temp_buf, size);
offset += size; if (offset >= buf_size) break;
} while (debug_move_entry(p_info, reverse));
debug_file_private_free(p_info);
buf[offset] = '\0';
/* append new element to linked list */ if (!debug_area_first) { /* first element in list */
debug_area_first = id;
id->prev = NULL;
} else { /* append element to end of list */
debug_area_last->next = id;
id->prev = debug_area_last;
}
debug_area_last = id;
id->next = NULL;
/** * debug_register_mode() - creates and initializes debug area. * * @name: Name of debug log (e.g. used for debugfs entry) * @pages_per_area: Number of pages, which will be allocated per area * @nr_areas: Number of debug areas * @buf_size: Size of data area in each debug entry * @mode: File mode for debugfs files. E.g. S_IRWXUGO * @uid: User ID for debugfs files. Currently only 0 is supported. * @gid: Group ID for debugfs files. Currently only 0 is supported. * * Return: * - Handle for generated debug area * - %NULL if register failed * * Allocates memory for a debug log. * Must not be called within an interrupt handler.
*/
debug_info_t *debug_register_mode(constchar *name, int pages_per_area, int nr_areas, int buf_size, umode_t mode,
uid_t uid, gid_t gid)
{
debug_info_t *rc = NULL;
/* Since debugfs currently does not support uid/gid other than root, */ /* we do not allow gid/uid != 0 until we get support for that. */ if ((uid != 0) || (gid != 0))
pr_warn("Root becomes the owner of all s390dbf files in sysfs\n");
BUG_ON(!initialized);
/** * debug_register() - creates and initializes debug area with default file mode. * * @name: Name of debug log (e.g. used for debugfs entry) * @pages_per_area: Number of pages, which will be allocated per area * @nr_areas: Number of debug areas * @buf_size: Size of data area in each debug entry * * Return: * - Handle for generated debug area * - %NULL if register failed * * Allocates memory for a debug log. * The debugfs file mode access permissions are read and write for user. * Must not be called within an interrupt handler.
*/
debug_info_t *debug_register(constchar *name, int pages_per_area, int nr_areas, int buf_size)
{ return debug_register_mode(name, pages_per_area, nr_areas, buf_size,
S_IRUSR | S_IWUSR, 0, 0);
}
EXPORT_SYMBOL(debug_register);
/** * debug_register_static() - registers a static debug area * * @id: Handle for static debug area * @pages_per_area: Number of pages per area * @nr_areas: Number of debug areas * * Register debug_info_t defined using DEFINE_STATIC_DEBUG_INFO. * * Note: This function is called automatically via an initcall generated by * DEFINE_STATIC_DEBUG_INFO.
*/ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas)
{ unsignedlong flags;
debug_info_t *copy;
if (!initialized) {
pr_err("Tried to register debug feature %s too early\n",
id->name); return;
}
/* Remove debugfs entries and remove from internal list. */ staticvoid _debug_unregister(debug_info_t *id)
{ int i;
for (i = 0; i < DEBUG_MAX_VIEWS; i++) { if (!id->views[i]) continue;
debugfs_remove(id->debugfs_entries[i]);
}
debugfs_remove(id->debugfs_root_entry); if (id == debug_area_first)
debug_area_first = id->next; if (id == debug_area_last)
debug_area_last = id->prev; if (id->prev)
id->prev->next = id->next; if (id->next)
id->next->prev = id->prev;
}
/** * debug_unregister() - give back debug area. * * @id: handle for debug log * * Return: * none
*/ void debug_unregister(debug_info_t *id)
{ if (!id) return;
mutex_lock(&debug_mutex);
_debug_unregister(id);
mutex_unlock(&debug_mutex);
/* * debug_set_size: * - set area size (number of pages) and number of areas
*/ staticint debug_set_size(debug_info_t *id, int nr_areas, int pages_per_area)
{
debug_info_t *new_id; unsignedlong flags;
/* * proceed_active_entry: * - set active entry to next in the ring buffer
*/ staticinlinevoid proceed_active_entry(debug_info_t *id)
{ if ((id->active_entries[id->active_area] += id->entry_size)
> (PAGE_SIZE - id->entry_size)) {
id->active_entries[id->active_area] = 0;
id->active_pages[id->active_area] =
(id->active_pages[id->active_area] + 1) %
id->pages_per_area;
}
}
/* * proceed_active_area: * - set active area to next in the ring buffer
*/ staticinlinevoid proceed_active_area(debug_info_t *id)
{
id->active_area++;
id->active_area = id->active_area % id->nr_areas;
}
/* Swap debug areas of a and b. */ staticvoid debug_areas_swap(debug_info_t *a, debug_info_t *b)
{
swap(a->nr_areas, b->nr_areas);
swap(a->pages_per_area, b->pages_per_area);
swap(a->areas, b->areas);
swap(a->active_area, b->active_area);
swap(a->active_pages, b->active_pages);
swap(a->active_entries, b->active_entries);
}
/* Append all debug events in active area from source to destination log. */ staticvoid debug_events_append(debug_info_t *dest, debug_info_t *src)
{
debug_entry_t *from, *to, *last;
if (!src->areas || !dest->areas) return;
/* Loop over all entries in src, starting with oldest. */
from = get_active_entry(src);
last = from; do { if (from->clock != 0LL) {
to = get_active_entry(dest);
memset(to, 0, dest->entry_size);
memcpy(to, from, min(src->entry_size,
dest->entry_size));
proceed_active_entry(dest);
}
proceed_active_entry(src);
from = get_active_entry(src);
} while (from != last);
}
/* * debug_finish_entry: * - set timestamp, caller address, cpu number etc.
*/
staticinlinevoid debug_finish_entry(debug_info_t *id, debug_entry_t *active, int level, int exception)
{ unsignedlong timestamp; union tod_clock clk;
/* * proc handler for the running debug_active sysctl * always allow read, allow write only if debug_stoppable is set or * if debug_active is already off
*/ staticint s390dbf_procactive(conststruct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos)
{ if (!write || debug_stoppable || !debug_active) return proc_dointvec(table, write, buffer, lenp, ppos); else return 0;
}
/** * debug_stop_all() - stops the debug feature if stopping is allowed. * * Return: * - none * * Currently used in case of a kernel oops.
*/ void debug_stop_all(void)
{ if (debug_stoppable)
debug_active = 0;
}
EXPORT_SYMBOL(debug_stop_all);
/** * debug_set_critical() - event/exception functions try lock instead of spin. * * Return: * - none * * Currently used in case of stopping all CPUs but the current one. * Once in this state, functions to write a debug entry for an * event or exception no longer spin on the debug area lock, * but only try to get it and fail if they do not get the lock.
*/ void debug_set_critical(void)
{
debug_critical = 1;
}
/* * debug_event_common: * - write debug entry with given size
*/
debug_entry_t *debug_event_common(debug_info_t *id, int level, constvoid *buf, int len)
{
debug_entry_t *active; unsignedlong flags;
if (!debug_active || !id->areas) return NULL; if (debug_critical) { if (!spin_trylock_irqsave(&id->lock, flags)) return NULL;
} else {
spin_lock_irqsave(&id->lock, flags);
} do {
active = get_active_entry(id);
memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); if (len < id->buf_size)
memset((DEBUG_DATA(active)) + len, 0, id->buf_size - len);
debug_finish_entry(id, active, level, 0);
len -= id->buf_size;
buf += id->buf_size;
} while (len > 0);
/* * debug_exception_common: * - write debug entry with given size and switch to next debug area
*/
debug_entry_t *debug_exception_common(debug_info_t *id, int level, constvoid *buf, int len)
{
debug_entry_t *active; unsignedlong flags;
if (!debug_active || !id->areas) return NULL; if (debug_critical) { if (!spin_trylock_irqsave(&id->lock, flags)) return NULL;
} else {
spin_lock_irqsave(&id->lock, flags);
} do {
active = get_active_entry(id);
memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); if (len < id->buf_size)
memset((DEBUG_DATA(active)) + len, 0, id->buf_size - len);
debug_finish_entry(id, active, level, len <= id->buf_size);
len -= id->buf_size;
buf += id->buf_size;
} while (len > 0);
if (user_len > 0x10000)
user_len = 0x10000; if (*offset != 0) {
rc = -EPIPE; goto out;
} if (copy_from_user(input_buf, user_buf, 1)) {
rc = -EFAULT; goto out;
} if (input_buf[0] == '-') {
debug_flush(id, DEBUG_FLUSH_ALL); goto out;
} if (isdigit(input_buf[0])) { int area = ((int) input_buf[0] - (int) '0');
debug_flush(id, area); goto out;
}
pr_info("Flushing debug data failed because %c is not a valid " "area\n", input_buf[0]);
out:
*offset += user_len; return rc; /* number of input characters */
}
/* * prints debug data in hex/ascii format
*/ staticint debug_hex_ascii_format_fn(debug_info_t *id, struct debug_view *view, char *out_buf, size_t out_buf_size, constchar *in_buf)
{ int i, rc = 0;
for (i = 0; i < id->buf_size; i++) {
rc += scnprintf(out_buf + rc, out_buf_size - rc, "%02x ", ((unsignedchar *)in_buf)[i]);
}
rc += scnprintf(out_buf + rc, out_buf_size - rc, "| "); for (i = 0; i < id->buf_size; i++) { unsignedchar c = in_buf[i];
/* * prints debug data sprintf-formatted: * debug_sprintf_event/exception calls must be used together with this view
*/
#define DEBUG_SPRINTF_MAX_ARGS 10
int debug_sprintf_format_fn(debug_info_t *id, struct debug_view *view, char *out_buf, size_t out_buf_size, constchar *inbuf)
{
debug_sprintf_entry_t *curr_event = (debug_sprintf_entry_t *)inbuf; int num_longs, num_used_args = 0, i, rc = 0; int index[DEBUG_SPRINTF_MAX_ARGS];
/* count of longs fit into one entry */
num_longs = id->buf_size / sizeof(long);
if (num_longs < 1) goto out; /* bufsize of entry too small */ if (num_longs == 1) { /* no args, we use only the string */
rc = strscpy(out_buf, curr_event->string, out_buf_size); if (rc == -E2BIG)
rc = out_buf_size; goto out;
}
/* number of arguments used for sprintf (without the format string) */
num_used_args = min(DEBUG_SPRINTF_MAX_ARGS, (num_longs - 1));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.