上面分析到基本的读写操作通过ioctrl来调用对应的driver驱动的实现。riffa_driver.c比较大,52K,这里补贴全部源码,分析哪一段就截图哪一段。
里面内容比较多,很多是linux套路化的代码,我们通过ioctrl传递的参数来搜索,直奔主题.
我们搜索IOCTL_SEND,找到处理ioctrl的分支程序段:
在处理IOCTL_SEND的时候我们看到实现用户数据拷贝到内核空间之后调用了chnl_send_wrapcheck,将riffa.c打包过来的参数一一传递过去。好的,那就跟进分析chnl_send_wrapcheck。
这里看到其做了一些避免错误的判断(值得一提的是通过自旋锁避免多线程错误的判断让我们坐实了riffa驱动支持多线程)之后,调用了chnl_send。
/**
* Writes data to the FPGA channel specified. Will block until all the data is
* sent to the FPGA unless a non-zero timeout is configured. If timeout is non-
* zero, then the function will block until all data is sent or when the timeout
* ms elapses. User data from the bufp pointer will be sent, up to len words
* (each word == 32 bits). The channel will be told how much data to expect and
* at what offset. If last == 1, the FPGA channel will recognize this
* transaction as complete after sending. If last == 0, the FPGA channel will
* expect additional transactions. On success, returns the number of words sent.
* On error, returns a negative value.
*/
static inline unsigned int chnl_send(struct fpga_state * sc, int chnl,
const char __user * bufp, unsigned int len, unsigned int offset,
unsigned int last, unsigned long long timeout)
{
struct sg_mapping * sg_map;
long tymeouto;
long tymeout;
int nomsg;
unsigned int msg_type;
unsigned int msg;
unsigned long long sent = 0;
unsigned long long length = (((unsigned long long)len)<<2);
unsigned long udata = (unsigned long)bufp;
DEFINE_WAIT(wait);
// Convert timeout to jiffies.
tymeout = (timeout == 0 ? MAX_SCHEDULE_TIMEOUT : (timeout * HZ/1000 > LONG_MAX ? LONG_MAX : timeout * HZ/1000));
tymeouto = tymeout;
// Clear the message queue.
while (!pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg));//清除之前有发送完的队列.没有发送完的直接丢弃。这样合适吗?
// Initialize the sg_maps
sc->send[chnl]->sg_map_0 = NULL;
sc->send[chnl]->sg_map_1 = NULL;
// Let FPGA know about transfer.
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send (len:%d off:%d last:%d)\n", sc->id, chnl, len, offset, last);
write_reg(sc, CHNL_REG(chnl, RX_OFFLAST_REG_OFF), ((offset<<1) | last));
write_reg(sc, CHNL_REG(chnl, RX_LEN_REG_OFF), len);
if (len == 0)
return 0;
// Use the send common buffer to share the scatter gather data
sg_map = fill_sg_buf(sc, chnl, sc->send[chnl]->buf_addr, udata, length, 0, DMA_TO_DEVICE);
if (sg_map == NULL || sg_map->num_sg == 0)
return (unsigned int)(sent>>2);
// Update based on the sg_mapping
udata += sg_map->length;
length -= sg_map->length;
sc->send[chnl]->sg_map_1 = sg_map;
// Let FPGA know about the scatter gather buffer.
write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_LO_REG_OFF), (sc->send[chnl]->buf_hw_addr & 0xFFFFFFFF));
write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_HI_REG_OFF), ((sc->send[chnl]->buf_hw_addr>>32) & 0xFFFFFFFF));
write_reg(sc, CHNL_REG(chnl, RX_SG_LEN_REG_OFF), 4 * sg_map->num_sg);
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send sg buf populated, %d sent\n", sc->id, chnl, sg_map->num_sg);
// Continue until we get a message or timeout.
while (1) {
while ((nomsg = pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg))) {
prepare_to_wait(&sc->send[chnl]->waitq, &wait, TASK_INTERRUPTIBLE);
// Another check before we schedule.
if ((nomsg = pop_circ_queue(sc->send[chnl]->msgs, &msg_type, &msg)))
tymeout = schedule_timeout(tymeout);
finish_wait(&sc->send[chnl]->waitq, &wait);
if (signal_pending(current)) {
free_sg_buf(sc, sc->send[chnl]->sg_map_0);
free_sg_buf(sc, sc->send[chnl]->sg_map_1);
return -ERESTARTSYS;
}
if (!nomsg)
break;
if (tymeout == 0) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, send timed out\n", sc->id, chnl);
free_sg_buf(sc, sc->send[chnl]->sg_map_0);
free_sg_buf(sc, sc->send[chnl]->sg_map_1);
return (unsigned int)(sent>>2);
}
}
tymeout = tymeouto;
// Process the message.
switch (msg_type) {
case EVENT_SG_BUF_READ:
// Release the previous scatter gather data?
if (sc->send[chnl]->sg_map_0 != NULL)
sent += sc->send[chnl]->sg_map_0->length;
free_sg_buf(sc, sc->send[chnl]->sg_map_0);
sc->send[chnl]->sg_map_0 = NULL;
// Populate the common buffer with more scatter gather data?
if (length > 0) {
sg_map = fill_sg_buf(sc, chnl, sc->send[chnl]->buf_addr, udata, length, 0, DMA_TO_DEVICE);
if (sg_map == NULL || sg_map->num_sg == 0) {
free_sg_buf(sc, sc->send[chnl]->sg_map_0);
free_sg_buf(sc, sc->send[chnl]->sg_map_1);
return (unsigned int)(sent>>2);
}
// Update based on the sg_mapping
udata += sg_map->length;
length -= sg_map->length;
sc->send[chnl]->sg_map_0 = sc->send[chnl]->sg_map_1;
sc->send[chnl]->sg_map_1 = sg_map;
write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_LO_REG_OFF), (sc->send[chnl]->buf_hw_addr & 0xFFFFFFFF));
write_reg(sc, CHNL_REG(chnl, RX_SG_ADDR_HI_REG_OFF), ((sc->send[chnl]->buf_hw_addr>>32) & 0xFFFFFFFF));
write_reg(sc, CHNL_REG(chnl, RX_SG_LEN_REG_OFF), 4 * sg_map->num_sg);
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send sg buf populated, %d sent\n", sc->id, chnl, sg_map->num_sg);
}
break;
case EVENT_TXN_DONE:
// Update with the true value of words transferred.
sent = (((unsigned long long)msg)<<2);
// Return as this is the end of the transaction.
free_sg_buf(sc, sc->send[chnl]->sg_map_0);
free_sg_buf(sc, sc->send[chnl]->sg_map_1);
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, sent %d words\n", sc->id, chnl, (unsigned int)(sent>>2));
return (unsigned int)(sent>>2);
break;
default:
printk(KERN_ERR "riffa: fpga:%d chnl:%d, received unknown msg: %08x\n", sc->id, chnl, msg);
break;
}
}
return 0;
}
这代码让我们看明白了cire_queue的作用实际是沟通中断处理程序和用户成功。在中断程序里面调用push,在用户程序里面pop出队列获取。
///
// INTERRUPT HANDLER
///
/**
* Reads the interrupt vector and processes it. If processing VECT0, off will
* be 0. If processing VECT1, off will be 6.
*/
static inline void process_intr_vector(struct fpga_state * sc, int off,
unsigned int vect)
{
// VECT_0/VECT_1 are organized from right to left (LSB to MSB) as:
// [ 0] TX_TXN for channel 0 in VECT_0, channel 6 in VECT_1
// [ 1] TX_SG_BUF_RECVD for channel 0 in VECT_0, channel 6 in VECT_1
// [ 2] TX_TXN_DONE for channel 0 in VECT_0, channel 6 in VECT_1
// [ 3] RX_SG_BUF_RECVD for channel 0 in VECT_0, channel 6 in VECT_1
// [ 4] RX_TXN_DONE for channel 0 in VECT_0, channel 6 in VECT_1
// ...
// [25] TX_TXN for channel 5 in VECT_0, channel 11 in VECT_1
// [26] TX_SG_BUF_RECVD for channel 5 in VECT_0, channel 11 in VECT_1
// [27] TX_TXN_DONE for channel 5 in VECT_0, channel 11 in VECT_1
// [28] RX_SG_BUF_RECVD for channel 5 in VECT_0, channel 11 in VECT_1
// [29] RX_TXN_DONE for channel 5 in VECT_0, channel 11 in VECT_1
// Positions 30 - 31 in both VECT_0 and VECT_1 are zero.
unsigned int offlast;
unsigned int len;
int recv;
int send;
int chnl;
int i;
//printk(KERN_INFO "riffa: intrpt_handler received:%08x\n", vect);
if (vect & 0xC0000000) {
printk(KERN_ERR "riffa: fpga:%d, received bad interrupt vector:%08x\n", sc->id, vect);
return;
}
for (i = 0; i < 6 && (i+off) < sc->num_chnls; ++i) {
chnl = i + off;
recv = 0;
send = 0;
// TX (PC receive) scatter gather buffer is read.
if (vect & (1<<((5*i)+1))) {
recv = 1;
// Keep track so the thread can handle this.
if (push_circ_queue(sc->recv[chnl]->msgs, EVENT_SG_BUF_READ, 0)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, recv sg buf read msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, recv sg buf read\n", sc->id, chnl);
}
// TX (PC receive) transaction done.
if (vect & (1<<((5*i)+2))) {
recv = 1;
// Read the transferred amount.
len = read_reg(sc, CHNL_REG(chnl, TX_TNFR_LEN_REG_OFF));
// Notify the thread.
if (push_circ_queue(sc->recv[chnl]->msgs, EVENT_TXN_DONE, len)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, recv txn done msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, recv txn done\n", sc->id, chnl);
}
// New TX (PC receive) transaction.
if (vect & (1<<((5*i)+0))) {
recv = 1;
// Read the offset/last and length
offlast = read_reg(sc, CHNL_REG(chnl, TX_OFFLAST_REG_OFF));
len = read_reg(sc, CHNL_REG(chnl, TX_LEN_REG_OFF));
// Keep track of this transaction
if (push_circ_queue(sc->recv[chnl]->msgs, EVENT_TXN_OFFLAST, offlast)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, recv txn offlast msg queue full\n", sc->id, chnl);
}
if (push_circ_queue(sc->recv[chnl]->msgs, EVENT_TXN_LEN, len)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, recv txn len msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, recv txn (len:%d off:%d last:%d)\n", sc->id, chnl, len, (offlast>>1), (offlast & 0x1));
}
// RX (PC send) scatter gather buffer is read.
if (vect & (1<<((5*i)+3))) {
send = 1;
// Keep track so the thread can handle this.
if (push_circ_queue(sc->send[chnl]->msgs, EVENT_SG_BUF_READ, 0)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, send sg buf read msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send sg buf read\n", sc->id, chnl);
}
// RX (PC send) transaction done.
if (vect & (1<<((5*i)+4))) {
send = 1;
// Read the transferred amount.
len = read_reg(sc, CHNL_REG(chnl, RX_TNFR_LEN_REG_OFF));
// Notify the thread.
if (push_circ_queue(sc->send[chnl]->msgs, EVENT_TXN_DONE, len)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, send txn done msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send txn done\n", sc->id, chnl);
}
// Wake up the thread?
if (recv)
wake_up(&sc->recv[chnl]->waitq);
if (send)
wake_up(&sc->send[chnl]->waitq);
}
}
我们看到跟PC发送到FPGA相关的中断处理事件号有:
EVENT_TXN_DONE
EVENT_SG_BUF_READ
分别是发送已经结束,以及SG缓冲区已经被读。
EVENT_TXN_DONE,就是全部传输结束。
EVENT_SG_BUF_READ,就是部分数据被FPGA取走,还有继续填充页面发走其他数据。
进一步分析工作机制我们猜测一下发送数据的机制是需要让FPGA产生EVENT_SG_BUF_READ中断主动获取数据,每次获取一个内存页面的数据,这样一页一页传输。直到传输完成,就会产生一个EVENT_TXN_DONE的中断告知。
我们是带着问题分析这些代码的,就是要看一次发送环节中在什么环节浪费了非传输的时间,或者是等待的时间。现在就有了两种可能:
1,中断方式传输一页一页获取数据。这点基本可以排除,因为我们一次传输很大数组(几百M)时候,得到的传输速率更高,最高接近带宽的75%。远比多次传递小数组用时间总和小很多。所以认为这种中断获取的方式应该不是带宽瓶颈。这里同时我们看到中断处理里面有write_reg因此,应该可以猜测write_reg不会占据很多的时间
2,对FPGA寄存器的读写,这里我们就想应该是带宽瓶颈了,因为中断里面反复调用了write_reg,我们分析应该不会造成瓶颈。分析代码我们看基本都是对FPGA今寄存器进行写操作,只有一次读的操作就是在中断处理:
// RX (PC send) transaction done.
if (vect & (1<<((5*i)+4))) {
send = 1;
// Read the transferred amount.
len = read_reg(sc, CHNL_REG(chnl, RX_TNFR_LEN_REG_OFF));
// Notify the thread.
if (push_circ_queue(sc->send[chnl]->msgs, EVENT_TXN_DONE, len)) {
printk(KERN_ERR "riffa: fpga:%d chnl:%d, send txn done msg queue full\n", sc->id, chnl);
}
DEBUG_MSG(KERN_INFO "riffa: fpga:%d chnl:%d, send txn done\n", sc->id, chnl);
}
以上代码我们可以不读寄存器,屏蔽掉这段,直接返回一个我们约定的发送len,之后进行实验,看是否可以明显提高速度。
其实我么可以做实验,尝试计算一下这个读寄存器的时间,也可以计算一下写寄存器的实验,这样就可以确定瓶颈是否产生在这两个函数(read_reg和write_reg)上了。
另外突然想到另外一个方法我们可以在驱动里面加入毫秒级别的时间检测,看看是否在哪里憋的时间比较长,就可以进行修改。
上述猜想要进行实验确定。