2011-11-24 14:50:13 +01:00
|
|
|
/* This file implements block level tracing support. */
|
|
|
|
|
|
|
|
#include <minix/drivers.h>
|
|
|
|
#include <minix/blockdriver_mt.h>
|
|
|
|
#include <minix/btrace.h>
|
|
|
|
#include <sys/ioc_block.h>
|
|
|
|
#include <minix/minlib.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
2011-12-07 15:44:28 +01:00
|
|
|
#include "const.h"
|
2011-11-24 14:50:13 +01:00
|
|
|
#include "trace.h"
|
|
|
|
|
|
|
|
#define NO_TRACEDEV ((dev_t) -1)
|
|
|
|
#define NO_TIME ((u32_t) -1)
|
|
|
|
|
|
|
|
PRIVATE int trace_enabled = FALSE;
|
|
|
|
PRIVATE dev_t trace_dev = NO_TRACEDEV;
|
|
|
|
PRIVATE btrace_entry *trace_buf = NULL;
|
|
|
|
PRIVATE size_t trace_size = 0;
|
|
|
|
PRIVATE size_t trace_pos;
|
|
|
|
PRIVATE size_t trace_next;
|
|
|
|
PRIVATE u64_t trace_tsc;
|
|
|
|
|
|
|
|
/* Pointers to in-progress trace entries for each thread (all worker threads,
|
|
|
|
* plus one for the main thread). Each pointer is set to NULL whenever no
|
|
|
|
* operation is currently being traced for that thread, for whatever reason.
|
|
|
|
*/
|
2011-12-07 15:44:28 +01:00
|
|
|
PRIVATE btrace_entry *trace_ptr[MAX_THREADS + 1] = { NULL };
|
2011-11-24 14:50:13 +01:00
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* trace_gettime *
|
|
|
|
*===========================================================================*/
|
|
|
|
PRIVATE u32_t trace_gettime(void)
|
|
|
|
{
|
|
|
|
/* Return the current time, in microseconds since the start of the trace.
|
|
|
|
*/
|
|
|
|
u64_t tsc;
|
|
|
|
|
|
|
|
assert(trace_enabled);
|
|
|
|
|
|
|
|
read_tsc_64(&tsc);
|
|
|
|
|
|
|
|
tsc = sub64(tsc, trace_tsc);
|
|
|
|
|
|
|
|
return tsc_64_to_micros(tsc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* trace_ctl *
|
|
|
|
*===========================================================================*/
|
|
|
|
PUBLIC int trace_ctl(dev_t minor, unsigned int request, endpoint_t endpt,
|
|
|
|
cp_grant_id_t grant)
|
|
|
|
{
|
|
|
|
/* Process a block trace control request.
|
|
|
|
*/
|
|
|
|
size_t size;
|
|
|
|
int r, ctl, entries;
|
|
|
|
|
|
|
|
switch (request) {
|
|
|
|
case BIOCTRACEBUF:
|
|
|
|
/* The size cannot be changed when tracing is enabled. */
|
|
|
|
if (trace_enabled) return EBUSY;
|
|
|
|
|
|
|
|
/* Copy in the requested size. */
|
|
|
|
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &size,
|
|
|
|
sizeof(size), D)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (size >= INT_MAX / sizeof(btrace_entry)) return EINVAL;
|
|
|
|
|
|
|
|
/* The size can only be set or reset, not changed. */
|
|
|
|
if (size > 0 && trace_size > 0) return EBUSY;
|
|
|
|
|
|
|
|
/* Allocate or free a buffer for tracing data. For future multi-device
|
|
|
|
* tracing support, the buffer is associated with a minor device.
|
|
|
|
*/
|
|
|
|
if (size == 0) {
|
|
|
|
if (trace_dev == NO_TRACEDEV) return OK;
|
|
|
|
|
|
|
|
if (trace_dev != minor) return EINVAL;
|
|
|
|
|
|
|
|
free(trace_buf);
|
|
|
|
|
|
|
|
trace_dev = NO_TRACEDEV;
|
|
|
|
} else {
|
|
|
|
if ((trace_buf = malloc(size * sizeof(btrace_entry))) == NULL)
|
|
|
|
return errno;
|
|
|
|
|
|
|
|
trace_dev = minor;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_size = size;
|
|
|
|
trace_pos = 0;
|
|
|
|
trace_next = 0;
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
|
|
|
|
case BIOCTRACECTL:
|
|
|
|
/* We can only start/stop tracing if the given device has a trace
|
|
|
|
* buffer associated with it.
|
|
|
|
*/
|
|
|
|
if (trace_dev != minor) return EINVAL;
|
|
|
|
|
|
|
|
/* Copy in the request code. */
|
|
|
|
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &ctl,
|
|
|
|
sizeof(ctl), D)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/* Start or stop tracing. */
|
|
|
|
switch (ctl) {
|
|
|
|
case BTCTL_START:
|
|
|
|
if (trace_enabled) return EBUSY;
|
|
|
|
|
|
|
|
read_tsc_64(&trace_tsc);
|
|
|
|
|
|
|
|
trace_enabled = TRUE;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BTCTL_STOP:
|
|
|
|
if (!trace_enabled) return EINVAL;
|
|
|
|
|
|
|
|
trace_enabled = FALSE;
|
|
|
|
|
|
|
|
/* Cancel all ongoing trace operations. */
|
|
|
|
memset(trace_ptr, 0, sizeof(trace_ptr));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
|
|
|
|
case BIOCTRACEGET:
|
|
|
|
/* We can only retrieve tracing entries if the given device has a trace
|
|
|
|
* buffer associated with it.
|
|
|
|
*/
|
|
|
|
if (trace_dev != minor) return EINVAL;
|
|
|
|
|
|
|
|
if (trace_enabled) return EBUSY;
|
|
|
|
|
|
|
|
/* How much can we copy out? */
|
|
|
|
entries = MIN(trace_pos - trace_next,
|
|
|
|
_MINIX_IOCTL_SIZE_BIG(request) / sizeof(btrace_entry));
|
|
|
|
|
|
|
|
if (entries == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((r = sys_safecopyto(endpt, grant, 0,
|
|
|
|
(vir_bytes) &trace_buf[trace_next],
|
|
|
|
entries * sizeof(btrace_entry), D)) != OK)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
trace_next += entries;
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
|
|
|
return EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* trace_start *
|
|
|
|
*===========================================================================*/
|
|
|
|
PUBLIC void trace_start(thread_id_t id, message *m_ptr)
|
|
|
|
{
|
|
|
|
/* Start creating a trace entry.
|
|
|
|
*/
|
|
|
|
btrace_entry *entry;
|
|
|
|
int req;
|
|
|
|
u64_t pos;
|
|
|
|
size_t size;
|
|
|
|
int flags;
|
|
|
|
|
|
|
|
if (!trace_enabled || trace_dev != m_ptr->BDEV_MINOR) return;
|
|
|
|
|
2011-12-07 15:44:28 +01:00
|
|
|
assert(id >= 0 && id < MAX_THREADS + 1);
|
2011-11-24 14:50:13 +01:00
|
|
|
|
|
|
|
if (trace_pos == trace_size)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (m_ptr->m_type) {
|
|
|
|
case BDEV_OPEN: req = BTREQ_OPEN; break;
|
|
|
|
case BDEV_CLOSE: req = BTREQ_CLOSE; break;
|
|
|
|
case BDEV_READ: req = BTREQ_READ; break;
|
|
|
|
case BDEV_WRITE: req = BTREQ_WRITE; break;
|
|
|
|
case BDEV_GATHER: req = BTREQ_GATHER; break;
|
|
|
|
case BDEV_SCATTER: req = BTREQ_SCATTER; break;
|
|
|
|
case BDEV_IOCTL: req = BTREQ_IOCTL; break;
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (m_ptr->m_type) {
|
|
|
|
case BDEV_OPEN:
|
|
|
|
case BDEV_CLOSE:
|
|
|
|
pos = cvu64(0);
|
|
|
|
size = m_ptr->BDEV_ACCESS;
|
|
|
|
flags = 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BDEV_READ:
|
|
|
|
case BDEV_WRITE:
|
|
|
|
case BDEV_GATHER:
|
|
|
|
case BDEV_SCATTER:
|
|
|
|
pos = make64(m_ptr->BDEV_POS_LO, m_ptr->BDEV_POS_HI);
|
|
|
|
size = m_ptr->BDEV_COUNT;
|
|
|
|
flags = m_ptr->BDEV_FLAGS;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BDEV_IOCTL:
|
|
|
|
pos = cvu64(0);
|
|
|
|
size = m_ptr->BDEV_REQUEST;
|
|
|
|
flags = 0;
|
|
|
|
|
|
|
|
/* Do not log trace control requests. */
|
|
|
|
switch (size) {
|
|
|
|
case BIOCTRACEBUF:
|
|
|
|
case BIOCTRACECTL:
|
|
|
|
case BIOCTRACEGET:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* Do not log any other messages. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = &trace_buf[trace_pos];
|
|
|
|
entry->request = req;
|
|
|
|
entry->size = size;
|
|
|
|
entry->position = pos;
|
|
|
|
entry->flags = flags;
|
|
|
|
entry->result = BTRES_INPROGRESS;
|
|
|
|
entry->start_time = trace_gettime();
|
|
|
|
entry->finish_time = NO_TIME;
|
|
|
|
|
|
|
|
trace_ptr[id] = entry;
|
|
|
|
trace_pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* trace_setsize *
|
|
|
|
*===========================================================================*/
|
|
|
|
PUBLIC void trace_setsize(thread_id_t id, size_t size)
|
|
|
|
{
|
|
|
|
/* Set the current trace entry's actual (byte) size, for vector requests.
|
|
|
|
*/
|
|
|
|
btrace_entry *entry;
|
|
|
|
|
|
|
|
if (!trace_enabled) return;
|
|
|
|
|
2011-12-07 15:44:28 +01:00
|
|
|
assert(id >= 0 && id < MAX_THREADS + 1);
|
2011-11-24 14:50:13 +01:00
|
|
|
|
|
|
|
if ((entry = trace_ptr[id]) == NULL) return;
|
|
|
|
|
|
|
|
entry->size = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*===========================================================================*
|
|
|
|
* trace_finish *
|
|
|
|
*===========================================================================*/
|
|
|
|
PUBLIC void trace_finish(thread_id_t id, int result)
|
|
|
|
{
|
|
|
|
/* Finish a trace entry.
|
|
|
|
*/
|
|
|
|
btrace_entry *entry;
|
|
|
|
|
|
|
|
if (!trace_enabled) return;
|
|
|
|
|
2011-12-07 15:44:28 +01:00
|
|
|
assert(id >= 0 && id < MAX_THREADS + 1);
|
2011-11-24 14:50:13 +01:00
|
|
|
|
|
|
|
if ((entry = trace_ptr[id]) == NULL) return;
|
|
|
|
|
|
|
|
entry->result = result;
|
|
|
|
entry->finish_time = trace_gettime();
|
|
|
|
|
|
|
|
trace_ptr[id] = NULL;
|
|
|
|
}
|