lib/libblockdriver/trace.c
/* 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>

#include "const.h"
#include "trace.h"

#define NO_TRACEDEV		((dev_t) -1)
#define NO_TIME			((u32_t) -1)

static int trace_enabled	= FALSE;
static dev_t trace_dev		= NO_TRACEDEV;
static btrace_entry *trace_buf	= NULL;
static size_t trace_size	= 0;
static size_t trace_pos;
static size_t trace_next;
static 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.
 */
static btrace_entry *trace_ptr[MAX_THREADS + 1] = { NULL };

/*===========================================================================*
 *				trace_gettime				     *
 *===========================================================================*/
static 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				     *
 *===========================================================================*/
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))) != 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))) != 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))) != OK)
		return r;

	trace_next += entries;

	return entries;
  }

  return EINVAL;
}

/*===========================================================================*
 *				trace_start				     *
 *===========================================================================*/
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;

  assert(id >= 0 && id < MAX_THREADS + 1);

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

  assert(id >= 0 && id < MAX_THREADS + 1);

  if ((entry = trace_ptr[id]) == NULL) return;

  entry->size = size;
}

/*===========================================================================*
 *				trace_finish				     *
 *===========================================================================*/
void trace_finish(thread_id_t id, int result)
{
/* Finish a trace entry.
 */
  btrace_entry *entry;

  if (!trace_enabled) return;

  assert(id >= 0 && id < MAX_THREADS + 1);

  if ((entry = trace_ptr[id]) == NULL) return;

  entry->result = result;
  entry->finish_time = trace_gettime();

  trace_ptr[id] = NULL;
}
