// Copyright(c) 2017, Intel Corporation
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Intel Corporation nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//****************************************************************************
/// @file mml_debug_link_linux.cpp
/// @brief Basic AFU interaction.
/// @ingroup SigTap
/// @verbatim
//****************************************************************************
#include <fcntl.h>
#include <cerrno>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include "mm_debug_link_linux.h"
#define DRIVER_PATH "/dev/mm_debug_link"
#define B2P_EOP 0x7B
#define BASE_ADDR 4096
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)
#define MM_DEBUG_LINK_DATA_WRITE 0x100
#define MM_DEBUG_LINK_WRITE_CAPACITY 0x104
#define MM_DEBUG_LINK_DATA_READ 0x108
#define MM_DEBUG_LINK_READ_CAPACITY 0x10C
#define MM_DEBUG_LINK_FIFO_WRITE_COUNT 0x120
#define MM_DEBUG_LINK_FIFO_READ_COUNT 0x140
#define MM_DEBUG_LINK_ID_ROM 0x160
#define MM_DEBUG_LINK_SIGNATURE 0x170
#define MM_DEBUG_LINK_VERSION 0x174
#define MM_DEBUG_LINK_DEBUG_RESET 0x178
#define MM_DEBUG_LINK_MGMT_INTF 0x17C
#define REMSTP_MMIO_RD_LEN 0x180
#define REMSTP_MMIO_WR_LEN 0x184
#define REMSTP_RESET 0x188
#define LEN_8B 0x2
#define LEN_4B 0x1
#define LEN_1B 0x0
//#define DEBUG_8B_4B_TRANSFERS 1 // Uncomment for 4B/8B DBG
//#define DEBUG_FLAG 1 //Uncomment to enable read/write information
using namespace std;
/*
* The value to expect at offset MM_DEBUG_LINK_SIGNATURE, aka "SysC".
*/
#define EXPECT_SIGNATURE 0x53797343
/*
* The maximum version this driver supports.
*/
#define MAX_SUPPORTED_VERSION 1
mm_debug_link_interface *get_mm_debug_link(void)
{
return new mm_debug_link_linux();
}
mm_debug_link_linux::mm_debug_link_linux() {
m_fd = -1;
m_buf_end = 0;
m_write_fifo_capacity = 0;
m_write_before_any_read_rfifo_level = false;
m_last_read_rfifo_level_empty_time = 0;
m_read_rfifo_level_empty_interval = 1;
map_base = NULL;
}
int mm_debug_link_linux::open(unsigned char* stpAddr)
{
unsigned int sign, version;
m_fd = -1;
map_base = stpAddr;
cout << "Remote STP : Assert Reset" << endl << flush;
write_mmr(REMSTP_RESET, 'w', 0x1);
cout << "Remote STP : De-Assert Reset" << endl << flush;
write_mmr(REMSTP_RESET, 'w', 0x0);
sign = read_mmr<unsigned int>(MM_DEBUG_LINK_SIGNATURE);
cout << "Read signature value " << std::hex << sign << " to hw\n" << flush;
if ( sign != EXPECT_SIGNATURE)
{
cerr << "Unverified Signature\n";
return -1;
}
version = read_mmr<unsigned int>(MM_DEBUG_LINK_VERSION);
cout << "Read version value " << version << " to hw\n";
if ( version > MAX_SUPPORTED_VERSION )
{
cerr << "Unsupported Version\n";
return -1;
}
this->m_write_fifo_capacity = read_mmr<int>(MM_DEBUG_LINK_WRITE_CAPACITY);
cout << "Read write fifo capacity value " << std::dec << this->m_write_fifo_capacity << " to hw\n";
return 0;
}
void mm_debug_link_linux::write_mmr(off_t target,
int access_type,
uint64_t write_val)
{
#ifdef DEBUG_8B_4B_TRANSFERS
cout << hex <<"WRITING : "<< write_val << dec << endl;
#endif
void *virt_addr;
/* Map one page */
virt_addr = (void *) (map_base + target);
switch(access_type) {
case 'b':
*((uint8_t *) virt_addr) = write_val;
break;
case 'h':
*((uint16_t *) virt_addr) = write_val;
break;
case 'w':
*((uint32_t *) virt_addr) = write_val;
break;
case 'q':
*((uint64_t *) virt_addr) = write_val;
break;
default:
cerr << "Illegal data type '" << access_type << "'.\n";
exit(2);
}
}
bool mm_debug_link_linux::can_read_data()
{
bool ret = this->m_write_before_any_read_rfifo_level;
if ( !ret )
{
clock_t cur_time = ::clock();
clock_t duration = cur_time - this->m_last_read_rfifo_level_empty_time;
if ( duration < 0 )
{
duration = -duration;
}
if ( duration >= this->m_read_rfifo_level_empty_interval )
{
ret = true;
}
}
return ret;
}
ssize_t mm_debug_link_linux::read()
{
uint8_t num_bytes;
num_bytes = read_mmr<uint8_t>(MM_DEBUG_LINK_FIFO_READ_COUNT);
// Reset the timer record
if ( (this->m_write_before_any_read_rfifo_level || // when this is the first read after write
num_bytes > 0) ) // when something is available to read
{
this->m_write_before_any_read_rfifo_level = false;
this->m_read_rfifo_level_empty_interval = 1; // Increase the read fifo level polling freq. in anticipation of more read data availability.
}
if (num_bytes > 0 )
{
if ( num_bytes > (mm_debug_link_linux::BUFSIZE - m_buf_end) )
{
num_bytes = mm_debug_link_linux::BUFSIZE - m_buf_end;
}
#ifdef DEBUG_FLAG
cout << "Read " << num_bytes << " bytes\n";
#endif
/*
==========================================================================================================================
At this point, num_bytes has the No. of bytes available to read from the FPGA
Default implementation: Tries to pull 1B at a time.
The Objective is to increase link utilization (1/8) to (8/8):
-------------------------------------------------------------
The interface on HW to SLD HUB Controller system still supports 1B reads/ writes only
The solution is to avoid 1B ping-pong and communicate to remote STP soft logic on HW with No. of bytes to read (say N)
The HW should read so many bytes (N) from the SLD HUB Cont Sys and return a packed read response. (little endian 64b max payload)
A register (REMSTP_MMIO_RD_LEN) is defined @ PORT DFH offset 0x4180 - Default value is 0. Possible values are 0/1/2.
Only SW can modify this register. SW should always retain the last value written to this register
SW always does 8B/4B/1B MMIO reads to MM_DEBUG_LINK_DATA_READ register (depending on the current value of N)
RemoteSTP logic on HW translates this to REMSTP_MMIO_RD_LEN (N) number of 1B reads from SLD HUB Cont sys endpoint
HW returns a 1B/4B/8B read value. Only lower REMSTP_MMIO_RD_LEN bytes are valid
SW should drop the remaining upper bytes of the returned response and update the mem-mapped pointer.
SW is responsible for credit control on the HW read FIFO
i.e. SW should update REMSTP_MMIO_RD_LEN register based on num_bytes available to read
Leaving back entries in the FIFO/ popping an empty FIFO is FATAL
Similarly, on the Write Path number of bytes to be written to HW write FIFO is packed into 4B/8B writes whenever possible
A register (REMSTP_MMIO_WR_LEN) is defined @ PORT DFH offset 0x4184 - Default value is 0. Possible values are 0/1/2. (M say)
RemoteSTP logic on HW will replay 1B writes M times into SLD HUB controller system w/o SLD endpoint.
Encodings for REMSTP_MMIO_WR_LEN & REMSTP_MMIO_RD_LEN
-----------------------------------------------------
-------------------------
| Encoding | Rd/Wr Len |
-------------------------
| 2'b00 | 1 |
| 2'b01 | 4 |
| 2'b10 | 8 |
| 2'b11 | Rsvd |
-------------------------
Performance impact:
-------------------
1)
OLD - Time to read 8bytes = 8 * 1 MMIO Read latency from device
NEW - Time to read 8bytes (best case) = 1 MMIO Read latency from device
NEW - Time to read 8bytes (worst case) = 1 MMIO Write latency to device + 1 MMIO Read latency from device
2) Through efficient utilization of MMIO data width, the amount of degradation seen on available AFU BW when using remoteSTP could be lowered
NOTE:
-----
MMIO reads to REMSTP_MMIO_RD_LEN or REMSTP_MMIO_WR_LEN is NOT supported
*/
uint8_t num_8B_reads, num_4B_reads, num_1B_reads, remaining_bytes;
num_8B_reads = num_bytes/8;
remaining_bytes = num_bytes%8;
num_4B_reads = remaining_bytes/4;
remaining_bytes = remaining_bytes%4;
num_1B_reads = remaining_bytes;
#ifdef DEBUG_8B_4B_TRANSFERS
cout << dec;
cout << "DBG_READ : Total_Bytes = " << (unsigned) num_bytes << " ; 8_bytes = "
<< (unsigned) num_8B_reads << " ; 4_bytes = " << (unsigned) num_4B_reads << " ; 1_bytes = " << (unsigned) num_1B_reads << endl << flush;
#endif
// SW should update HW control (REMSTP_MMIO_RD_LEN) and use only REMSTP_MMIO_RD_LEN bytes returned
if (num_8B_reads > 0)
{
// Change REMSTP_MMIO_RD_LEN to 8B
write_mmr( REMSTP_MMIO_RD_LEN, 'w', LEN_8B);
for ( unsigned char i = 0; i < num_8B_reads; ++i )
{
volatile uint64_t *p = reinterpret_cast<volatile uint64_t *>(this->m_buf +
this->m_buf_end +
(i*8));
*p = read_mmr<uint64_t>(MM_DEBUG_LINK_DATA_READ);
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_READ_8B : Iteration "<< (unsigned) i << "; READ VALUE : " << hex;
for (unsigned char j=0; j<8; j++)
cout << (int) *( this->m_buf + this->m_buf_end + (i*8) + j ) << flush;
cout << dec << endl << flush;
#endif
}
}
if (num_4B_reads > 0)
{
// Change REMSTP_MMIO_RD_LEN to 4B
write_mmr( REMSTP_MMIO_RD_LEN, 'w', LEN_4B);
for ( unsigned char i = 0; i < num_4B_reads; ++i )
{
volatile uint32_t *p = reinterpret_cast<volatile uint32_t *>(this->m_buf +
this->m_buf_end +
(num_8B_reads*8) +
(i*4));
*p = read_mmr<uint32_t>(MM_DEBUG_LINK_DATA_READ);
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_READ_4B : Iteration "<< (int) i << "; READ VALUE : " << hex;
for (unsigned char j=0; j<4; j++)
cout << (int) *( this->m_buf + this->m_buf_end + ( (num_8B_reads*8) + (i*4) + j)) << flush;
cout << dec << endl << flush;
#endif
}
}
if (num_1B_reads > 0)
{
// Change REMSTP_MMIO_RD_LEN to 1B
write_mmr( REMSTP_MMIO_RD_LEN, 'w', LEN_1B);
for ( unsigned char i = 0; i < num_1B_reads; ++i )
{
volatile uint8_t *p = reinterpret_cast<volatile uint8_t *>(this->m_buf + this->m_buf_end +
(num_8B_reads*8) +
(num_4B_reads*4) +
i);
*p = read_mmr<uint8_t>(MM_DEBUG_LINK_DATA_READ);
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_READ_1B : Iteration "<< (int) i << "; READ VALUE : "
<< hex << (int) *( this->m_buf + this->m_buf_end + ( (num_8B_reads*8) + (num_4B_reads*4) +i)) << endl << flush << dec;
#endif
}
}
// ==========================================================================================================================
unsigned int x;
for ( unsigned char i = 0; i < num_bytes; ++i )
{
x = this->m_buf[this->m_buf_end + i];
#ifdef DEBUG_FLAG
cout << setfill('0') << setw(2) << std::hex << x << " ";
#else
UNUSED_PARAM(x);
#endif
}
#ifdef DEBUG_FLAG
cout << std::dec << "\n";
#endif
this->m_buf_end += num_bytes;
}
else
{
//printf( "%s %s(): error read hw read buffer level\n", __FILE__, __FUNCTION__ );
num_bytes = 0;
this->m_last_read_rfifo_level_empty_time = ::clock();
//Throttle the read rfifo level polling freq. up to 10 sec.
this->m_read_rfifo_level_empty_interval *= 2;
if ( this->m_read_rfifo_level_empty_interval >= 10 * CLOCKS_PER_SEC )
{
this->m_read_rfifo_level_empty_interval = 10 * CLOCKS_PER_SEC;
}
}
return num_bytes;
}
ssize_t mm_debug_link_linux::write(const void *buf, size_t count)
{
uint8_t num_bytes;
unsigned int x;
num_bytes = read_mmr<uint8_t>(MM_DEBUG_LINK_FIFO_WRITE_COUNT);
this->m_write_before_any_read_rfifo_level = true; // Set this to kick off any possible read activity even if write FIFO is full to avoid potential deadlock.
if ( num_bytes < this->m_write_fifo_capacity )
{
num_bytes = this->m_write_fifo_capacity - num_bytes;
if ( count < num_bytes )
{
num_bytes = count;
}
count = 0;
// ==========================================================================================================================
uint8_t num_8B_writes, num_4B_writes, num_1B_writes, remaining_bytes;
num_8B_writes = num_bytes/8;
remaining_bytes = num_bytes%8;
num_4B_writes = remaining_bytes/4;
remaining_bytes = remaining_bytes%4;
num_1B_writes = remaining_bytes;
#ifdef DEBUG_8B_4B_TRANSFERS
cout << dec << endl;
cout << "DBG_WRITE : Total_Bytes = " << (unsigned) num_bytes << " ; 8_bytes = " << (unsigned) num_8B_writes
<< " ; 4_bytes = " << (unsigned) num_4B_writes << " ; 1_bytes = " << (unsigned) num_1B_writes << endl << flush;
#endif
// SW should update HW control (REMSTP_MMIO_WR_LEN) and use only REMSTP_MMIO_WR_LEN bytes returned
if (num_8B_writes > 0)
{
// Change REMSTP_MMIO_WR_LEN to 8B
write_mmr( REMSTP_MMIO_WR_LEN, 'w', LEN_8B);
for ( size_t i = 0; i < num_8B_writes; ++i )
{
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_WRITE_8B : Iteration "<< i << "; ";
#endif
write_mmr( MM_DEBUG_LINK_DATA_WRITE, 'q', *( (uint64_t *)buf + i ) );
count+=8;
}
}
if (num_4B_writes > 0)
{
// Change REMSTP_MMIO_WR_LEN to 4B
write_mmr( REMSTP_MMIO_WR_LEN, 'w', LEN_4B);
for ( size_t i = 0; i < num_4B_writes; ++i )
{
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_WRITE_4B : Iteration "<< i << "; ";
#endif
write_mmr( MM_DEBUG_LINK_DATA_WRITE, 'w', *( (uint32_t *)buf + ( (num_8B_writes*2) + i ) ) );
count+=4;
}
}
if (num_1B_writes > 0)
{
// Change REMSTP_MMIO_WR_LEN to 1B
write_mmr( REMSTP_MMIO_WR_LEN, 'w', LEN_1B);
for ( size_t i = 0; i < num_1B_writes; ++i )
{
#ifdef DEBUG_8B_4B_TRANSFERS
cout << "DBG_WRITE_1B : Iteration "<< i << "; ";
#endif
write_mmr( MM_DEBUG_LINK_DATA_WRITE, 'b', *( (unsigned char *)buf + ( (num_8B_writes*8) + (num_4B_writes*4) + (i) ) ) );
++count;
}
}
// ==========================================================================================================================
num_bytes = count;
#ifdef DEBUG_FLAG
cout << "Wrote " << num_bytes << " bytes\n";
#endif
for ( int i = 0; i < num_bytes; ++i )
{
x = *((unsigned char *)buf + i);
#ifdef DEBUG_FLAG
cout << setfill('0') << setw(2) << std::hex << x << " ";
#else
UNUSED_PARAM(x);
#endif
}
#ifdef DEBUG_FLAG
cout << std::dec << "\n" ;
#endif
}
else
{
//cerr << "Error write hw write buffer level\n";
num_bytes = 0;
}
return num_bytes;
}
void mm_debug_link_linux::close(void)
{
if(munmap((void *) map_base, MAP_SIZE) == -1){
cerr << "Unmap error\n";
}
if (m_fd != -1){
::close(m_fd);
}
m_fd = -1;
}
void mm_debug_link_linux::write_ident(int val)
{
write_mmr(MM_DEBUG_LINK_ID_ROM, 'b', val);
cout << "Write mixer value " << val << " to hw\n";
}
void mm_debug_link_linux::reset(bool val)
{
unsigned int reset_val = val ? 1 : 0;
write_mmr(MM_DEBUG_LINK_DEBUG_RESET, 'w', val);
cout << "Write reset value " << reset_val << " to hw\n";
}
void mm_debug_link_linux::ident(int id[4])
{
for ( int i = 0; i < 4; i++ )
{
id[i] = read_mmr<int>(MM_DEBUG_LINK_ID_ROM + i * 4);
}
}
void mm_debug_link_linux::enable(int channel, bool state)
{
int encoded_cmd = (channel << 8) | (state ? 1 : 0);
write_mmr(MM_DEBUG_LINK_MGMT_INTF, 'w', encoded_cmd);
cout << "Enable channel " << encoded_cmd << " to hw\n";
}
bool mm_debug_link_linux::flush_request(void)
{
bool should_flush = false;
if (m_buf_end == BUFSIZE)
// Full buffer? Send.
should_flush = true;
else if (memchr(m_buf, B2P_EOP, m_buf_end - 1))
// Buffer contains eop? Send.
// If the eop character occurs in the very last buffer byte, there's no packet here -
// we need at least one more byte.
// Interesting corner case: it's not strictly true that one more byte after EOP indicates
// the end of a packet - that byte after EOP might be the escape character. In this case,
// we flush even though it's not necessarily a complete packet. This probably has negligible
// impact on performance.
should_flush = true;
if ( m_buf_end > 0 )
{
should_flush = true;
}
return should_flush;
}