/* Copyright (C) 2014 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Tom DeCanio * \author Ken Steele, Tilera Corporation * * File-like output for logging on Tilera PCIe cards (TILEncore-Gx) * add the option to send logs across PCIe and then write the output * files on the host system. * */ #include #include "suricata-common.h" /* errno.h, string.h, etc. */ #include "tm-modules.h" /* LogFileCtx */ #include "conf.h" /* ConfNode, etc. */ #include "util-atomic.h" #include "util-logopenfile-tile.h" #ifdef __tile__ #include #include #if MDE_VERSION_CODE >= MDE_VERSION(4,1,0) #include #else #include #endif /* * Tilera trio (PCIe) configuration. */ static gxio_trio_context_t trio_context_body; static gxio_trio_context_t* trio_context = &trio_context_body; /* * gxpci contexts used for log relay */ static gxpci_context_t gxpci_context_body; static gxpci_context_t *gxpci_context = &gxpci_context_body; /* The TRIO index. */ static int trio_index = 0; /* The queue index of a packet queue. */ static unsigned int queue_index = 0; /* The local PCIe MAC index. */ static int loc_mac; /* * Code for writing files over PCIe to host on Tilera TILEncore PCIe cards. */ #define OP_OPEN 1 #define OP_WRITE 2 #define OP_CLOSE 3 /** Maximum number of commands in one PCIe function call */ #define MAX_CMDS_BATCH 64 typedef struct { uint32_t magic; uint32_t fileno; uint32_t op; uint32_t seq; uint32_t len; uint32_t next_offset; char buf[]; } __attribute__((__packed__)) PcieMsg; static int gxpci_fileno = 0; static int pcie_initialized = 0; /* Allocate a Huge page of memory, registered with Trio, into which data to be sent over PCIe is written. Each write starts at wc_pos. */ static char *log_mem = NULL; static uint64_t wr_pos; /* write position within log_mem */ static SCMutex raw_mutex __attribute__((aligned(64))); static SCMutex pcie_mutex __attribute__((aligned(64))); #define CHECK_SEQ_NUM 1 #ifdef CHECK_SEQ_NUM static uint32_t raw_seq = 0; #endif static uint32_t comps_rcvd = 0; /* Block of memory registered with PCIe DMA engine as a source for * PCIe data transfers. Must be <= Huge Page size (16 MB). * Must be large enough that it can't wrap before first PCIe transfer * has completed. */ #define PCIE_MEMORY_BLOCK_SIZE (4 * 1024 * 1024) /* Send a buffer over PCIe to Host memory. * len must be smaller than one Packet Queue transfer block. * TODO: Check errors */ static void TilePcieDMABuf(void *buf, uint32_t len) { gxpci_comp_t comp[MAX_CMDS_BATCH]; gxpci_cmd_t cmd; int result; int credits; SCMutexLock(&pcie_mutex); #ifdef CHECK_SEQ_NUM ((PcieMsg *)buf)->seq = ++raw_seq; __insn_mf(); #endif /* Wait for credits to be available for more PCIe writes. */ do { result = gxpci_get_comps(gxpci_context, comp, 0, MAX_CMDS_BATCH); if (result) { if (unlikely(result == GXPCI_ERESET)) { SCLogInfo("gxpci channel is reset"); return; } else { __sync_fetch_and_add(&comps_rcvd, result); } } credits = gxpci_get_cmd_credits(gxpci_context); if (unlikely(credits == GXPCI_ERESET)) { SCLogInfo("gxpci channel is reset"); return; } } while (credits == 0); cmd.buffer = buf; /* Round transfer size up to next host cache-line. This will * transfer more data, but is more efficient. */ cmd.size = (len + (CLS - 1)) & ~(CLS - 1); __insn_mf(); /* Loop until the command is sent. */ do { /* Send PCIe command to packet queue from tile to host. */ result = gxpci_pq_t2h_cmd(gxpci_context, &cmd); if (result == 0) break; if (result == GXPCI_ERESET) { SCLogInfo("gxpci channel is reset"); break; } /* Not enough credits to send command? */ if (result == GXPCI_ECREDITS) continue; } while (1); SCMutexUnlock(&pcie_mutex); } /* Allocate a buffer for data that can be sent over PCIe. Reserves * space at the beginning for the Pcie msg. The buffer is allocated * from a 4MB pool on one huge page. The allocation simply walks * throught the buffer sequentially. This removes the need to free * the buffers, as they simply age out. */ static PcieMsg *TilePcieAllocateBuffer(size_t size) { size += sizeof(PcieMsg); /* Round up to cache-line size */ size = (size + (CLS - 1)) & ~(CLS - 1); PcieMsg *pmsg; SCMutexLock(&raw_mutex); pmsg = (PcieMsg *)&log_mem[wr_pos]; wr_pos += size; if (wr_pos > PCIE_MEMORY_BLOCK_SIZE) { /* Don't have enough space at the end of the memory block, so * wrap to the start. */ pmsg = (PcieMsg *)&log_mem[0]; wr_pos = size; } SCMutexUnlock(&raw_mutex); return pmsg; } static void PcieWriteOpen(PcieFile *fp, const char *path, const char append) { /* Need space for file name, file mode character and string termination */ const int buffer_size = strlen(path) + 2; /* Allocate space in the PCIe output buffer */ PcieMsg *p = TilePcieAllocateBuffer(buffer_size); p->magic = 5555; p->fileno = fp->fileno; p->op = OP_OPEN; p->len = offsetof(PcieMsg, buf); /* Format is one character Mode, followed by file path. */ p->len += snprintf(p->buf, buffer_size, "%c%s", append, path); TilePcieDMABuf(p, p->len); } static int TilePcieWrite(const char *buffer, int buffer_len, LogFileCtx *log_ctx) { PcieFile *fp = log_ctx->pcie_fp; /* Allocate space in the PCIe output buffer */ PcieMsg *p = TilePcieAllocateBuffer(buffer_len); p->magic = 5555; p->fileno = fp->fileno; p->op = OP_WRITE; p->len = offsetof(PcieMsg, buf); p->len += buffer_len; p->next_offset = 0; /* Can remove the need for this memcpy later. */ memcpy(p->buf, buffer, buffer_len); TilePcieDMABuf(p, p->len); return buffer_len; } static PcieFile *TileOpenPcieFpInternal(const char *path, const char append_char) { int result; PcieFile *fp; /* Only initialize once */ if (SCAtomicCompareAndSwap(&pcie_initialized, 0, 1)) { SCMutexInit(&raw_mutex, NULL); SCMutexInit(&pcie_mutex, NULL); SCLogInfo("Initializing Tile-Gx PCIe index %d / %d, queue: %d", trio_index, loc_mac, queue_index); result = gxio_trio_init(trio_context, trio_index); if (result < 0) { pcie_initialized = 0; SCLogError(SC_ERR_PCIE_INIT_FAILED, "gxio_trio_init() failed: %d: %s", result, gxio_strerror(result)); return NULL; } result = gxpci_init(trio_context, gxpci_context, trio_index, loc_mac); if (result < 0) { pcie_initialized = 0; SCLogError(SC_ERR_PCIE_INIT_FAILED, "gxpci_init() failed: %d: %s", result, gxpci_strerror(result)); return NULL; } /* * This indicates that we need to allocate an ASID ourselves, * instead of using one that is allocated somewhere else. */ int asid = GXIO_ASID_NULL; result = gxpci_open_queue(gxpci_context, asid, GXPCI_PQ_T2H, 0, queue_index, 0, 0); if (result < 0) { pcie_initialized = 0; SCLogError(SC_ERR_PCIE_INIT_FAILED, "gxpci_open_queue() failed: %d: %s", result, gxpci_strerror(result)); return NULL; } /* * Allocate and register data buffer */ size_t hugepagesz = tmc_alloc_get_huge_pagesize(); tmc_alloc_t alloc = TMC_ALLOC_INIT; tmc_alloc_set_huge(&alloc); tmc_alloc_set_home(&alloc, TMC_ALLOC_HOME_HASH); tmc_alloc_set_pagesize_exact(&alloc, hugepagesz); log_mem = tmc_alloc_map(&alloc, hugepagesz); BUG_ON(PCIE_MEMORY_BLOCK_SIZE > hugepagesz); result = gxpci_iomem_register(gxpci_context, log_mem, hugepagesz); if (result < 0) { pcie_initialized = 0; SCLogError(SC_ERR_PCIE_INIT_FAILED, "gxpci_iomem_register() failed: %d: %s", result, gxpci_strerror(result)); return NULL; } } fp = SCMalloc(sizeof(PcieFile)); if (fp == NULL) { SCLogError(SC_ERR_PCIE_INIT_FAILED, "Failed to Allocate memory for PCIe file pointer"); return NULL; } /* Sequentially allocate File descriptor numbers. Not currently ever freed */ fp->fileno = SCAtomicFetchAndAdd(&gxpci_fileno, 1); PcieWriteOpen(fp, path, append_char); return fp; } /** \brief Close a PCIe file * \param PCIe file desriptor */ static void TileClosePcieFp(LogFileCtx *log_ctx) { SCLogInfo("Closing Tile-Gx PCIe: %s", log_ctx->filename); /* TODO: Need to count open files and close when reaches zero. */ SCMutexLock(&pcie_mutex); if (gxpci_context) { gxpci_destroy(gxpci_context); gxpci_context = NULL; } SCMutexUnlock(&pcie_mutex); free(log_ctx->pcie_fp); } /** \brief open the indicated file remotely over PCIe to a host * \param path filesystem path to open * \param append_setting open file with O_APPEND: "yes" or "no" * \retval FILE* on success * \retval NULL on error */ PcieFile *TileOpenPcieFp(LogFileCtx *log_ctx, const char *path, const char *append_setting) { PcieFile *ret = NULL; if (strcasecmp(append_setting, "yes") == 0) { ret = TileOpenPcieFpInternal(path, 'a'); } else { ret = TileOpenPcieFpInternal(path, 'w'); } /* Override the default Write and Close functions * with PCIe Write and Close functions. */ log_ctx->Write = TilePcieWrite; log_ctx->Close = TileClosePcieFp; if (ret == NULL) SCLogError(SC_ERR_FOPEN, "Error opening PCIe file: \"%s\": %s", path, strerror(errno)); return ret; } #endif /* __tilegx__ */