Commit 8a2fe92a authored by Federico Vaga's avatar Federico Vaga

wrnc: add support for debug messages

Signed-off-by: Federico Vaga's avatarFederico Vaga <federico.vaga@cern.ch>
parent c2e894f0
......@@ -418,6 +418,27 @@ struct wrnc_smem_io {
};
\end{lstlisting}
\subsubsection{Debug Real-Time Application}%%%%%%%%%%%%%%%%%%%%%%%%%
Real-Time applications are able to send debug messages to the user
through the driver. The driver export a \textit{debugfs} char device
that allow you to read messages coming from a specific Real-Time
application running on a CPU. On this char-device you can only
\texttt{read(2)} and \texttt{poll(2)}. Data coming from this char-device
are simple characters' string \texttt{NULL} terminated. In order words,
the Real-Time application does \texttt{printf} and you can read the
string printed by the application.
You can find the char device in the \textit{debugfs} under the path:
\begin{verbatim}
/sys/kernel/debug/wrnc-%04x/wrnc-%04x-cpu-%02d-dbg
\end{verbatim}
Rembember that the \textit{debugfs} usually is not auto-mounted. In
order to mount \textit{debugfs}:
\begin{verbatim}
mount -t debugfs none /sys/kernel/debug/
\end{verbatim}
\subsection{Tools}%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Under the directory \texttt{tools} you can find the following
......
......@@ -28,6 +28,7 @@ obj-m := wr-node-core.o
wr-node-core-y := wrnc-core.o
wr-node-core-y += wrnc-cpu.o
wr-node-core-y += wrnc-hmq.o
wr-node-core-y += wrnc-dbg.o
all modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules
......
......@@ -12,6 +12,7 @@
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/byteorder/generic.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
......@@ -508,6 +509,7 @@ static int wrnc_probe_hmq(struct wrnc_dev *wrnc, unsigned int slot,
int wrnc_probe(struct fmc_device *fmc)
{
struct wrnc_dev *wrnc;
char tmp_name[128];
int err, i;
uint32_t tmp;
......@@ -544,6 +546,13 @@ int wrnc_probe(struct fmc_device *fmc)
if (err)
return err;
wrnc->dbg_dir = debugfs_create_dir(dev_name(&wrnc->dev), NULL);
if (IS_ERR_OR_NULL(wrnc->dbg_dir)) {
pr_err("UAL: Cannot create debugfs\n");
err = PTR_ERR(wrnc->dbg_dir);
goto out_dbg;
}
/* Get the Application ID */
wrnc->app_id = fmc_readl(fmc, wrnc->base_csr + WRN_CPU_CSR_REG_APP_ID);
......@@ -567,6 +576,7 @@ int wrnc_probe(struct fmc_device *fmc)
/* Configure CPUs */
for (i = 0; i < wrnc->n_cpu; ++i) {
wrnc->cpu[i].index = i;
spin_lock_init(&wrnc->cpu[i].lock);
err = wrnc_minor_get(&wrnc->cpu[i].dev, WRNC_CPU);
if (err)
......@@ -582,6 +592,13 @@ int wrnc_probe(struct fmc_device *fmc)
err = device_register(&wrnc->cpu[i].dev);
if (err)
goto out_cpu;
snprintf(tmp_name, 128, "%s-dbg", dev_name(&wrnc->cpu[i].dev));
wrnc->cpu[i].dbg_msg = debugfs_create_file(tmp_name, 0444,
wrnc->dbg_dir,
&wrnc->cpu[i],
&wrnc_cpu_dbg_fops);
if (IS_ERR_OR_NULL(wrnc->cpu[i].dbg_msg))
dev_err(&wrnc->cpu[i].dev, "Cannot create debug interface\n");
}
/* Get and check the number of HMQ slots */
......@@ -637,6 +654,19 @@ int wrnc_probe(struct fmc_device *fmc)
fmc_writel(fmc, wrnc->irq_mask, wrnc->base_gcr + MQUEUE_GCR_IRQ_MASK);
tmp = fmc_readl(fmc, wrnc->base_gcr + MQUEUE_GCR_IRQ_MASK);
/* Enable debug interrupts */
fmc->irq = wrnc->base_core + 1;
err = fmc->op->irq_request(fmc, wrnc_irq_handler_debug,
(char *)dev_name(&wrnc->cpu[i].dev),
0 /*VIC is used */);
if (err) {
dev_err(&wrnc->dev,
"Cannot request IRQ 0x%x - we'll not receive debug messages\n",
fmc->irq);
}
fmc_writel(fmc, 0xFFFFFFFF/*(wrnc->n_cpu - 1)*/,
wrnc->base_csr + WRN_CPU_CSR_REG_DBG_IMSK);
return 0;
out_hmq_out:
......@@ -652,6 +682,8 @@ out_cpu:
while (--i)
device_unregister(&wrnc->cpu[i].dev);
out_n_cpu:
debugfs_remove_recursive(wrnc->dbg_dir);
out_dbg:
device_unregister(&wrnc->dev);
return err;
}
......@@ -665,9 +697,15 @@ int wrnc_remove(struct fmc_device *fmc)
int i;
fmc_writel(fmc, 0x0, wrnc->base_gcr + MQUEUE_GCR_IRQ_MASK);
fmc->irq = 0xC0000;
fmc->irq = wrnc->base_core;
fmc->op->irq_free(fmc);
fmc_writel(fmc, 0x0, wrnc->base_csr + WRN_CPU_CSR_REG_DBG_IMSK);
fmc->irq = wrnc->base_core + 1;
fmc->op->irq_free(fmc);
debugfs_remove_recursive(wrnc->dbg_dir);
for (i = 0; i < wrnc->n_cpu; ++i)
device_unregister(&wrnc->cpu[i].dev);
......
/*
* Copyright (C) 2014 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@cern.ch>
* License: GPL v2
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/circ_buf.h>
#include <linux/fmc.h>
#include <hw/wrn_cpu_csr.h>
#include "wrnc.h"
int dbg_max_msg = 1024; /**< debug messages buffer */
module_param_named(max_dbg_msg, dbg_max_msg, int, 0444);
MODULE_PARM_DESC(max_dbg_msg, "Maximum number of debug messages in driver queue.");
static int wrnc_dbg_open(struct inode *inode, struct file *file)
{
struct wrnc_cpu *cpu;
if (inode->i_private)
file->private_data = inode->i_private;
cpu = file->private_data;
cpu->cbuf.buf = kmalloc(dbg_max_msg, GFP_KERNEL);
if (!cpu->cbuf.buf)
return -ENOMEM;
cpu->cbuf.head = 0;
cpu->cbuf.tail = 0;
return 0;
}
static int wrnc_dbg_close(struct inode *inode, struct file *file)
{
struct wrnc_cpu *cpu = file->private_data;
spin_lock(&cpu->lock);
kfree(cpu->cbuf.buf);
cpu->cbuf.buf = NULL;
spin_unlock(&cpu->lock);
return 0;
}
static ssize_t wrnc_dbg_read(struct file *f, char __user *buf,
size_t count, loff_t *offp)
{
struct wrnc_cpu *cpu = f->private_data;
size_t lcount;
int i;
spin_lock(&cpu->lock);
/* Check if there are char to send */
lcount = CIRC_CNT(cpu->cbuf.head, cpu->cbuf.tail, dbg_max_msg);
if (!lcount) {
spin_unlock(&cpu->lock);
return 0;
}
/* Copy to user the minumum number of char */
lcount = min(lcount, count);
for (i = 0; i < lcount; i++)
if (cpu->cbuf.buf[i] == '\0')
break;
lcount = i;
if (copy_to_user(buf, cpu->cbuf.buf + cpu->cbuf.tail, lcount)) {
spin_unlock(&cpu->lock);
return -EFAULT;
}
/* Consume char from the tail */
cpu->cbuf.tail = (cpu->cbuf.tail + lcount) & (dbg_max_msg - 1);
spin_unlock(&cpu->lock);
return count;
}
static unsigned int wrnc_dbg_poll(struct file *f, struct poll_table_struct *w)
{
struct wrnc_cpu *cpu = f->private_data;
dev_dbg(&cpu->dev, "%s head=%d, tail=%d\n",
cpu->cbuf.head, cpu->cbuf.tail);
if (CIRC_CNT(cpu->cbuf.head, cpu->cbuf.tail, dbg_max_msg))
return POLLIN | POLLRDNORM;
return 0;
}
const struct file_operations wrnc_cpu_dbg_fops = {
.owner = THIS_MODULE,
.open = wrnc_dbg_open,
.release = wrnc_dbg_close,
.read = wrnc_dbg_read,
.poll = wrnc_dbg_poll,
};
irqreturn_t wrnc_irq_handler_debug(int irq_core_base, void *arg)
{
struct fmc_device *fmc = arg;
struct wrnc_dev *wrnc = fmc_get_drvdata(fmc);
struct circ_buf *cb;
uint32_t status;
char c;
int i;
status = fmc_readl(fmc, wrnc->base_csr + WRN_CPU_CSR_REG_DBG_POLL);
do_irq:
i = -1;
while (status && ++i < wrnc->n_cpu) {
if (!(status & 0x1)) {
status >>= 1;
continue;
}
/* Select the CPU to use */
spin_lock(&wrnc->cpu[i].lock);
fmc_writel(fmc, i, wrnc->base_csr + WRN_CPU_CSR_REG_CORE_SEL);
c = fmc_readl(fmc,
wrnc->base_csr + WRN_CPU_CSR_REG_DBG_MSG);
cb = &wrnc->cpu[i].cbuf;
if (cb->buf) {
/* We cans store the char */
pr_debug("%s:%d %d=%c\n", __func__, __LINE__, i, c);
cb->buf[cb->head] = c;
cb->head = (cb->head + 1) & (dbg_max_msg - 1);
if (cb->head == cb->tail) {
cb->tail = (cb->tail + 1) & (dbg_max_msg - 1);
}
}
spin_unlock(&wrnc->cpu[i].lock);
}
status = fmc_readl(fmc, wrnc->base_csr + WRN_CPU_CSR_REG_DBG_POLL);
if (status)
goto do_irq;
fmc->op->irq_ack(fmc);
return IRQ_HANDLED;
}
......@@ -193,6 +193,7 @@ static struct wrnc_msg *wrnc_message_pop(struct wrnc_hmq *hmq)
msg->data[i] = fmc_readl(fmc,
hmq->base_sr + MQUEUE_SLOT_DATA_START + i * 4);
}
/* Discard the slot content */
fmc_writel(fmc, MQUEUE_CMD_DISCARD, hmq->base_sr + MQUEUE_SLOT_COMMAND);
spin_unlock_irqrestore(&hmq->lock, flags);
......@@ -226,7 +227,6 @@ static int wrnc_ioctl_msg_sync(struct wrnc_hmq *hmq, void __user *uarg)
return -EINVAL;
}
hmq_out = &wrnc->hmq_out[msg.index_out];
/*
* Wait until the message queue is empty so we can safely enqueue
* the synchronous message. Get the mutex to avoid other process
......
......@@ -7,6 +7,7 @@
#ifndef __WRNC_H__
#define __WRNC_H__
#include <linux/circ_buf.h>
#include "hw/mqueue.h"
#include "wrnc-user.h"
......@@ -65,9 +66,12 @@ struct wrnc_hmq {
* It describes a single instance of a CPU of the WRNC
*/
struct wrnc_cpu {
struct device dev; /**< device representing a single CPU */
int index; /**< instance number */
struct device dev; /**< device representing a single CPU */
struct dentry *dbg_msg; /**< debug messages interface */
struct circ_buf cbuf; /**< debug circular buffer */
struct spinlock lock;
struct wrnc_hmq *hmq[WRNC_MAX_HMQ_SLOT]; /**< list of HMQ slots used by
this CPU */
};
......@@ -95,15 +99,19 @@ struct wrnc_dev {
uint32_t irq_mask; /**< IRQ mask in use */
enum wrnc_smem_modifier mod; /**< smem operation modifier */
struct dentry *dbg_dir; /**< root debug directory */
};
/* Global data */
extern struct device *minors[WRNC_MAX_MINORS];
/* CPU data */
extern const struct file_operations wrnc_cpu_dbg_fops;
extern const struct file_operations wrnc_cpu_fops;
extern const struct attribute_group *wrnc_cpu_groups[];
extern void wrnc_cpu_enable_set(struct wrnc_dev *wrnc, uint8_t mask);
extern void wrnc_cpu_reset_set(struct wrnc_dev *wrnc, uint8_t mask);
extern irqreturn_t wrnc_irq_handler_debug(int irq_core_base, void *arg);
/* HMQ */
extern int hmq_max_msg;
extern const struct attribute_group *wrnc_hmq_groups[];
......
......@@ -15,6 +15,7 @@ struct wrnc_desc {
char name[WRNC_NAME_LEN]; /**< Name of the device */
int fd_dev; /**< File Descriptor of the device */
int fd_cpu[WRNC_MAX_CPU]; /**< File Descriptor of the CPUs */
int fd_dbg[WRNC_MAX_CPU]; /**< File Descriptor for the CPUs debug */
int fd_hmq_in[WRNC_MAX_HMQ_SLOT]; /**< File Descriptor of the
input HMQ */
int fd_hmq_out[WRNC_MAX_HMQ_SLOT]; /**< File Descriptor of the
......
......@@ -935,3 +935,80 @@ char *wrnc_name_get(struct wrnc_dev *wrnc)
return wdesc->name;
}
/**
* It opens the debug message stream
* @param[in] wrnc device token
* @param[in] index CPU index
* @return a debug token on success, NULL otherwise and errno is set
* appropriately
*/
struct wrnc_dbg *wrnc_debug_open(struct wrnc_dev *wrnc, unsigned int index)
{
struct wrnc_desc *wdesc = (struct wrnc_desc *)wrnc;
struct wrnc_dbg *dbg;
char path[64];
dbg = malloc(sizeof(struct wrnc_dbg));
if (!dbg)
return NULL;
dbg->wrnc = wrnc;
dbg->cpu_index = index;
snprintf(path, 64, "/sys/kernel/debug/%s/%s-cpu-%02d-dbg",
wdesc->name, wdesc->name, index);
dbg->fd = open(path, O_RDONLY);
if (!dbg->fd) {
free(dbg);
return NULL;
}
return dbg;
}
/**
* It closes the debug message stream
* @param[in] dbg_tkn debug token
* @param[in] index CPU index
*/
void wrnc_debug_close(struct wrnc_dbg *dbg)
{
close(dbg->fd);
free(dbg);
dbg = NULL;
}
/**
* It retrieve a message from the debug channel. It fills the buffer with a
* NULL terminated string.
* @param[in] dbg_tkn debug token
* @param[out] buf where store incoming message
* @param[in] count maximum number of char to read (terminator included)
* @return number of byte read on success, -1 otherwise and errno is set
* appropriately
*/
int wrnc_debug_message_get(struct wrnc_dbg *dbg, char *buf, size_t count)
{
int n = 0, real_count = 0;
memset(buf, 0, count);
do {
n = read(dbg->fd, buf + real_count, count - real_count);
if (n <= 0)
return -1;
real_count += n;
/* check if the string from the CPU is shorter */
if (buf[real_count - 1] == '\0')
break;
} while (real_count < count);
/* Put a terminator */
buf[real_count - 1] = '\0';
return real_count;
}
......@@ -19,6 +19,17 @@ extern "C" {
struct wrnc_dev;
/**
* Debug descriptor. It is not obfuscated because it is meant for debugging
* purpose. This way, it leaves the user all the freedom to read/poll the
* debug channel as (s)he wants.
*/
struct wrnc_dbg {
struct wrnc_dev *wrnc; /**< token of the device */
unsigned int cpu_index; /**< CPU where read debug messages */
int fd; /**< file descriptor of the debug interface */
};
#define WRNC_NAME_LEN 12
#define WRNC_SYSFS_PATH_LEN 128
......@@ -125,6 +136,12 @@ extern int wrnc_smem_write(struct wrnc_dev *wrnc, uint32_t addr, uint32_t *data,
extern int wrnc_bind(struct wrnc_dev *wrnc, struct wrnc_msg_filter *flt,
unsigned int length);
extern struct wrnc_dbg *wrnc_debug_open(struct wrnc_dev *wrnc,
unsigned int index);
extern void wrnc_debug_close(struct wrnc_dbg *dbg);
extern int wrnc_debug_message_get(struct wrnc_dbg *dbg,
char *buf, size_t count);
#ifdef __cplusplus
};
#endif
......
......@@ -12,14 +12,25 @@
#include <libwrnc.h>
#include <getopt.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#define MAX_DEV 4
#define MAX_SLOT 32
#define MAX_CPU 8
static unsigned int slot_index[MAX_DEV][MAX_SLOT], idx_valid[MAX_DEV], cnt, n;
static uint32_t dev_id[MAX_DEV];
struct wrnc_thread_desc {
struct wrnc_dev *wrnc;
uint32_t dev_id;
int cpu_index[MAX_CPU];
int n_cpu;
int slot_index[MAX_SLOT];
int n_slot;
};
static unsigned int cnt, n;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int timestamp = 0;
......@@ -32,6 +43,7 @@ static void help()
fprintf(stderr, "-i slot index\n");
fprintf(stderr, "-n number of total messages to read. The default is 0 (infinite)\n");
fprintf(stderr, "-t print message timestamp\n");
fprintf(stderr, "-d <CPU index> show debug messages for given CPU\n");
fprintf(stderr, "-h show this help\n");
fprintf(stderr, "\n");
fprintf(stderr,
......@@ -88,46 +100,95 @@ static int dump_message(struct wrnc_dev *wrnc, unsigned int slot_index)
return 0;
}
void print_debug(struct wrnc_dbg *dbg)
{
int n;
char c[256];
n = wrnc_debug_message_get(dbg, c, 256);
if (n < 0)
return;
fprintf(stderr, "%s-cpu-%d: %s\n",
wrnc_name_get(dbg->wrnc), dbg->cpu_index, c);
}
/**
* pthread for each device. It dumps messages from slots
* @param[in] arg a pointer to the device index
*/
void *dump_thread(void *arg)
{
unsigned long idx = (unsigned long)arg, i;
struct pollfd p[MAX_SLOT];
struct wrnc_thread_desc *th_data = arg;
struct pollfd p[MAX_SLOT], p_dbg[MAX_CPU];
struct wrnc_dbg *wdbg[MAX_CPU];
struct wrnc_dev *wrnc;
int ret, err;
int ret, err, i;
/* Open the device */
wrnc = wrnc_open_by_fmc(dev_id[idx]);
wrnc = wrnc_open_by_fmc(th_data->dev_id);
if (!wrnc) {
fprintf(stderr, "Cannot open WRNC: %s\n", wrnc_strerror(errno));
pthread_exit(NULL);
}
/* Build the polling structures */
for (i = 0; i < idx_valid[idx]; ++i) {
err = wrnc_hmq_open(wrnc, slot_index[idx][i], WRNC_HMQ_OUTCOMING);
for (i = 0; i < th_data->n_slot; ++i) {
err = wrnc_hmq_open(wrnc, th_data->slot_index[i],
WRNC_HMQ_OUTCOMING);
if (err) {
fprintf(stderr, "Cannot open HMQ: %s\n",
wrnc_strerror(errno));
goto out;
goto out_slot;
}
p[i].fd = slot_index[idx][i];
p[i].fd = th_data->slot_index[i];
p[i].events = POLLIN | POLLERR;
}
/* If there, open all debug channels */
for (i = 0; i < th_data->n_cpu; i++) {
wdbg[i] = wrnc_debug_open(wrnc, th_data->cpu_index[i]);
if (!wdbg[i]) {
fprintf(stderr, "Cannot open WRNC debug channel: %s\n",
wrnc_strerror(errno));
goto out_dbg;
}
p_dbg[i].fd = wdbg[i]->fd;
p_dbg[i].events = POLLIN | POLLERR;
}
/* Start dumping messages */
while (n == 0 || n > cnt) {
/* Polling debug messages */
ret = poll(p_dbg, th_data->n_cpu, 1000);
switch (ret) {
default:
/* Dump from the slot */
for (i = 0; i < th_data->n_cpu; ++i) {
if (!(p_dbg[i].revents & POLLIN))
continue;
print_debug(wdbg[i]);
}
break;
case 0:
/* timeout */
break;
case -1:
/* error */
goto out;
break;
}
/* Polling slots */
ret = wrnc_slot_poll(wrnc, p, idx_valid[idx], 10000);
ret = wrnc_slot_poll(wrnc, p, th_data->n_slot, 10000);
switch (ret) {
default:
/* Dump from the slot */
for (i = 0; i < idx_valid[idx]; ++i) {
for (i = 0; i < th_data->n_slot; ++i) {
if (!(p[i].revents & POLLIN))
continue;
err = dump_message(wrnc, p[i].fd);
if (err)
continue;
......@@ -141,13 +202,19 @@ void *dump_thread(void *arg)
break;
case -1:
/* error */
pthread_exit(NULL);
break;
goto out;
}
}
out:
for (i = 0; i < idx_valid[idx]; ++i)
wrnc_hmq_close(wrnc, slot_index[idx][i], WRNC_HMQ_OUTCOMING);
out_dbg:
/* Close all debug channels */
for (i = 0; i < th_data->n_cpu; i++)
wrnc_debug_close(wdbg[i]);
out_slot:
/* Close all message slots */
for (i = 0; i < th_data->n_slot; ++i)
wrnc_hmq_close(wrnc, th_data->slot_index[i], WRNC_HMQ_OUTCOMING);
wrnc_close(wrnc);
return NULL;
}
......@@ -155,6 +222,7 @@ out:
int main(int argc, char *argv[])
{
struct wrnc_thread_desc th_data[MAX_DEV], *last;
unsigned long i;
unsigned int di = 0;
pthread_t tid[MAX_DEV];
......@@ -163,27 +231,27 @@ int main(int argc, char *argv[])
atexit(wrnc_exit);
for (i = 0; i < MAX_SLOT; i++)
idx_valid[i] = 0;
memset(th_data, 0, sizeof(struct wrnc_thread_desc) * MAX_DEV);
while ((c = getopt (argc, argv, "hi:D:n:t")) != -1) {
while ((c = getopt (argc, argv, "hi:D:n:td:")) != -1) {
switch (c) {
default:
help();
break;
case 'i':
/* Save slot index for each device id */
if (di > 0 && idx_valid[di - 1] >= MAX_SLOT)
if (!last || last->n_slot >= MAX_SLOT)
break;
sscanf(optarg, "%d",
&slot_index[di - 1][idx_valid[di - 1]]);
idx_valid[di - 1]++;
&last->slot_index[last->n_slot]);
last->n_slot++;
break;
case 'D':
/* Save device ids to use */
if (di >= MAX_DEV)
break;
sscanf(optarg, "0x%x", &dev_id[di]);
last = &th_data[di];
sscanf(optarg, "0x%x", &last->dev_id);
di++;
break;
case 'n':
......@@ -193,6 +261,13 @@ int main(int argc, char *argv[])
case 't':
timestamp = 1;
break;
case 'd':
if (!last || last->n_cpu >= MAX_CPU)
break;
sscanf(optarg, "%d", &last->cpu_index[last->n_cpu]);
last->n_cpu++;
break;
}
}
......@@ -200,12 +275,13 @@ int main(int argc, char *argv[])
/* Run dumping on in parallel from several devices */
for (i = 0; i < di; i++) {
err = pthread_create(&tid[i], NULL, dump_thread, (void *)i);
err = pthread_create(&tid[i], NULL, dump_thread, (void *)(&th_data[i]));
if (err)
fprintf(stderr, "Cannot create 'dump_thread' instance %ld: %s\n",
i, strerror(errno));
}
/* Wait for the threads to finish */
for (i = 0; i < di; i++)
pthread_join(tid[i], NULL);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment