Commit f0587888 authored by Mikhail Karpenko's avatar Mikhail Karpenko

Merge branch 'multithreaded_aligned'

 Conflicts:
	src/camogm.c
	src/camogm.h
parents 676ade6a 7e3873de
......@@ -11,7 +11,7 @@ IMAGES = $(GUIDIR)/images/filebrowser-01.gif $(GUIDIR)/images/filebrowser-bo
$(GUIDIR)/images/rec_folder.png $(GUIDIR)/images/up_folder.gif $(GUIDIR)/images/play_audio.png $(GUIDIR)/images/hdd.png
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c camogm_align.c
TEST_SRC = camogm_test.c
OBJS = $(SRCS:.c=.o)
......@@ -31,7 +31,7 @@ IMAGEDIR = $(WWW_PAGES)/images
all: $(PROGS) $(TEST_PROG)
$(PROGS): $(OBJS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(TEST_PROG): $(TEST_SRC:.c=.o)
......
......@@ -64,21 +64,6 @@
char trailer[TRAILER_SIZE] = { 0xff, 0xd9 };
#if 0
const char *exifFileNames[] = { "/dev/exif_exif0", "/dev/exif_exif1",
"/dev/exif_exif2", "/dev/exif_exif3"
};
const char *headFileNames[] = { "/dev/jpeghead0", "/dev/jpeghead1",
"/dev/jpeghead2", "/dev/jpeghead3"
};
const char *ctlFileNames[] = { "/dev/frameparsall0", "/dev/frameparsall1",
"/dev/frameparsall2", "/de/framepars3"
};
const char *circbufFileNames[] = {"/dev/circbuf0", "/dev/circbuf1",
"/dev/circbuf2", "/dev/circbuf3"
};
#else
const char *exifFileNames[] = { DEV393_PATH(DEV393_EXIF0), DEV393_PATH(DEV393_EXIF1),
DEV393_PATH(DEV393_EXIF2), DEV393_PATH(DEV393_EXIF3)
};
......@@ -92,7 +77,6 @@ const char *ctlFileNames[] = { DEV393_PATH(DEV393_FRAMEPARS0), DEV393_PATH(DEV3
const char *circbufFileNames[] = {DEV393_PATH(DEV393_CIRCBUF0), DEV393_PATH(DEV393_CIRCBUF1),
DEV393_PATH(DEV393_CIRCBUF2), DEV393_PATH(DEV393_CIRCBUF3)
};
#endif
int lastDaemonBit[SENSOR_PORTS] = {DAEMON_BIT_CAMOGM};
struct framepars_all_t *frameParsAll[SENSOR_PORTS];
......@@ -167,6 +151,7 @@ static int get_sysfs_name(const char *dev_name, char *sys_name, size_t str_sz, i
static int get_disk_range(const char *name, struct range *rng);
static int set_disk_range(const struct range *rng);
static void get_disk_info(camogm_state *state);
static struct timeval get_fpga_time(const int fd_fparsall, unsigned int port);
int open_files(camogm_state *state);
unsigned long getGPValue(unsigned int port, unsigned long GPNumber);
void setGValue(unsigned int port, unsigned long GNumber, unsigned long value);
......@@ -279,6 +264,10 @@ void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num)
state->active_chn = ALL_CHN_INACTIVE;
state->rawdev.mmap_default_size = MMAP_CHUNK_SIZE;
state->sock_port = port_num;
state->writer_params.data_ready = false;
state->writer_params.exit_thread = false;
state->writer_params.state = STATE_STOPPED;
}
/**
......@@ -366,6 +355,7 @@ int camogm_start(camogm_state *state)
if (is_chn_active(state, chn)) {
// Check/set circbuf read pointer
D3(fprintf(debug_file, "1: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
D3(fprintf(debug_file, "1a: compressed frame number = %li\n", lseek(state->fd_circ[chn], LSEEK_CIRC_GETFRAME, SEEK_END)));
if ((state->cirbuf_rp[chn] < 0) || (lseek(state->fd_circ[chn], state->cirbuf_rp[chn], SEEK_SET) < 0) || (lseek(state->fd_circ[chn], LSEEK_CIRC_VALID, SEEK_END) < 0 )) {
D3(fprintf(debug_file, "2: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
/* In "greedy" mode try to save as many frames from the circbuf as possible */
......@@ -526,6 +516,9 @@ int sendImageFrame(camogm_state *state)
int * ifp_this = (int*)&(state->this_frame_params[state->port_num]);
int fp;
int port = state->port_num;
struct timeval start_time, end_time;
// start_time = get_fpga_time(state->fd_fparmsall[port], port);
// This is probably needed only for Quicktime (not to exceed already allocated frame index)
if (!state->rawdev_op && (state->frameno >= (state->max_frames))) {
......@@ -656,13 +649,16 @@ int sendImageFrame(camogm_state *state)
/* copy from the beginning of the buffer to the end of the frame */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len - (state->circ_buff_size[port] - state->cirbuf_rp[port]);
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][0];
state->writer_params.segments = 2;
} else { // single segment
D3(fprintf(debug_file, "_11_"));
/* copy from the beginning of the frame to the end of the frame (no buffer rollovers) */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp[port] >> 2];
state->writer_params.segments = 1;
}
D3(fprintf(debug_file, "\tcirbuf_rp = 0x%x\t", state->cirbuf_rp[port]));
D3(fprintf(debug_file, "_12_"));
state->packetchunks[state->chunk_index ].bytes = 2;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)trailer;
......@@ -685,6 +681,7 @@ int sendImageFrame(camogm_state *state)
// advance frame pointer
state->frameno++;
state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
D3(fprintf(debug_file, "\tcompressed frame number: %li\t", lseek(state->fd_circ[port], LSEEK_CIRC_GETFRAME, SEEK_END)));
// optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
D3(fprintf(debug_file, "_15_\n"));
......@@ -693,6 +690,19 @@ int sendImageFrame(camogm_state *state)
} else if (state->frames_skip < 0) {
state->frames_skip_left[port] += -(state->frames_skip);
}
D3(fprintf(debug_file,"cirbuf_rp to next frame = 0x%x\n", state->cirbuf_rp[port]));
// end_time = get_fpga_time(state->fd_fparmsall[port], port);
// unsigned int mbps; // write speed, MB/s
// unsigned long long time_diff; // time elapsed, in microseconds
// time_diff = ((end_time.tv_sec * 1000000 + end_time.tv_usec) - (start_time.tv_sec * 1000000 + start_time.tv_usec));
// mbps = ((double)state->rawdev.last_jpeg_size / (double)1048576) / ((double)time_diff / (double)1000000);
// D6(fprintf(debug_file, "Frame start time: %ld:%ld; frame end time: %ld:%ld; last frame size: %lu\n",
// start_time.tv_sec, start_time.tv_usec,
// end_time.tv_sec, end_time.tv_usec,
// state->rawdev.last_jpeg_size));
// D6(fprintf(debug_file, "Write speed: %d MB/s\n", mbps));
return 0;
}
......@@ -750,7 +760,7 @@ void camogm_free(camogm_state *state)
switch (f) {
case CAMOGM_FORMAT_NONE: break;
case CAMOGM_FORMAT_OGM: camogm_free_ogm(); break;
case CAMOGM_FORMAT_JPEG: camogm_free_jpeg(); break;
case CAMOGM_FORMAT_JPEG: camogm_free_jpeg(state); break;
case CAMOGM_FORMAT_MOV: camogm_free_mov(); break;
}
}
......@@ -875,6 +885,9 @@ void get_disk_info(camogm_state *state)
}
if (get_disk_range(state->rawdev.rawdev_path, &rng) == 0) {
state->writer_params.lba_start = rng.from;
state->writer_params.lba_end = rng.to;
state->writer_params.lba_current = state->writer_params.lba_start;
set_disk_range(&rng);
} else {
D0(fprintf(debug_file, "ERROR: unable to get disk size and starting sector\n"));
......@@ -2029,7 +2042,7 @@ int main(int argc, char *argv[])
sstate.rawdev.thread_state = STATE_RUNNING;
str_len = strlen(state_name_str);
if (str_len > 0) {
strncpy(sstate.rawdev.state_path, state_name_str, str_len + 1);
strncpy(sstate.rawdev.state_path, (const char *)state_name_str, str_len + 1);
}
ret = listener_loop(&sstate);
......@@ -2104,3 +2117,19 @@ inline int is_fd_valid(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
/**
* @brief Get current FPGA time
* @return Time value in \e timeval structure
*/
struct timeval get_fpga_time(const int fd_fparsall, unsigned int port)
{
struct timeval tv;
unsigned long write_data[] = {FRAMEPARS_GETFPGATIME, 0};
write(fd_fparsall, write_data, sizeof(unsigned long) * 2);
tv.tv_sec = getGPValue(port, G_SECONDS);
tv.tv_usec = getGPValue(port, G_MICROSECONDS);
return tv;
}
......@@ -27,7 +27,6 @@
#include <elphel/c313a.h>
#include <elphel/x393_devices.h>
#define CAMOGM_FRAME_NOT_READY 1 ///< frame pointer valid, but not yet acquired
#define CAMOGM_FRAME_INVALID 2 ///< invalid frame pointer
#define CAMOGM_FRAME_CHANGED 3 ///< frame parameters have changed
......@@ -66,6 +65,8 @@
#define MMAP_CHUNK_SIZE 10485760
/** @brief Time interval (in microseconds) for processing commands */
#define COMMAND_LOOP_DELAY 500000
/** @brief File can be split up to this number of chunks */
#define FILE_CHUNKS_NUM 8
/**
* @enum state_flags
......@@ -125,6 +126,7 @@ typedef struct {
uint64_t mmap_offset;
uint64_t file_start;
int64_t total_rec_len;
unsigned long last_jpeg_size;
pthread_t tid;
volatile int thread_state;
unsigned char *disk_mmap;
......@@ -132,6 +134,33 @@ typedef struct {
char state_path[ELPHEL_PATH_MAX];
} rawdev_buffer;
/**
* @struct writer_params
* @brief Contains mutexes and conditional variables associated with disk writing thread
*/
struct writer_params {
int blockdev_fd; ///< file descriptor for open block device where frame will be recorded
pthread_t writer_thread; ///< disk writing thread
pthread_mutex_t writer_mutex; ///< synchronization mutex for main and writing threads
pthread_cond_t writer_cond; ///< conditional variable indicating that writer thread can proceed with new frame
pthread_cond_t main_cond; ///< conditional variable indicating that main thread can update write pointers
bool data_ready; ///< flag indicating that new frame is ready for recording, access to this flag
///< must be protected with #writer_mutex. Set this flag in main thread and reset in
///< disk writing thread.
int last_ret_val; ///< error value return during last frame recording (if any occurred)
bool exit_thread; ///< flag indicating that the writing thread should terminate
int state; ///< the state of disk writing thread
int segments; ///< the number of segments in frame
struct iovec *data_chunks; ///< a set of vectors pointing to aligned frame data buffers
struct iovec prev_rem_vect; ///< vector pointing to the remainder of the previous frame
unsigned char *rem_buff; ///< buffer containing the unaligned remainder of the current frame
unsigned char *prev_rem_buff; ///< buffer containing the unaligned remainder of the previous frame
unsigned char *common_buff; ///< buffer for aligned JPEG header
uint64_t lba_start; ///< disk starting LBA
uint64_t lba_current; ///< current write position in LBAs
uint64_t lba_end; ///< disk last LBA
};
/**
* @struct camogm_state
* @brief Holds current state of the running program
......@@ -192,7 +221,7 @@ typedef struct {
int formats; ///< bitmask of used (initialized) formats
int format; ///< output file format
int set_format; ///< output format to set (will be updated after stop)
elph_packet_chunk packetchunks[8];
elph_packet_chunk packetchunks[FILE_CHUNKS_NUM];
int chunk_index;
int buf_overruns[SENSOR_PORTS];
int buf_min[SENSOR_PORTS];
......@@ -222,7 +251,7 @@ typedef struct {
rawdev_buffer rawdev; ///< contains pointers to raw device buffer
unsigned int active_chn; ///< bitmask of active sensor ports
uint16_t sock_port; ///< command socket port number
struct writer_params writer_params; ///< contains control parameters for writing thread
unsigned int error_stat[SENSOR_PORTS][CAMOGM_ERRNUM]; ///< collect statistics about errors
} camogm_state;
......
/** @file camogm_align.c
* @brief Provides frame alignment functions use for recording to block device.
* @copyright Copyright (C) 2017 Elphel, Inc.
*
* @par <b>License</b>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** @brief This define is needed to use lseek64 and should be set before includes */
#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/uio.h>
#include "camogm_align.h"
static unsigned char app15[ALIGNMENT_SIZE] = {0xff, 0xef};
static inline size_t get_size_from(const struct iovec *vects, int index, size_t offset, int all);
static inline size_t align_bytes_num(size_t data_len, size_t align_len);
static inline void vectcpy(struct iovec *dest, void *src, size_t len);
static inline void vectshrink(struct iovec *vec, size_t len);
static inline unsigned char *vectrpos(struct iovec *vec, size_t offset);
static void dev_dbg(const char *prefix, const char *format, ...);
static void remap_vectors(camogm_state *state, struct iovec *chunks);
static size_t get_blocks_num(struct iovec *sgl, size_t n_elem);
/* debug functions */
static int check_chunks(struct iovec *vects);
/** Replace debug function with the same name from driver code with debug macro to reduce changes */
static void dev_dbg(const char *prefix, const char *format, ...)
{
va_list args;
va_start(args, format);
D6(vfprintf(debug_file, format, args));
va_end(args);
}
/** Copy @e len bytes from buffer pointed by @e src vector to buffer pointed by @e dest vector */
static inline void vectcpy(struct iovec *dest, void *src, size_t len)
{
unsigned char *d = (unsigned char *)dest->iov_base;
memcpy(d + dest->iov_len, src, len);
dest->iov_len += len;
}
/** Shrink vector length by @len bytes */
static inline void vectshrink(struct iovec *vec, size_t len)
{
if (vec->iov_len >= len) {
vec->iov_len -= len;
}
}
/** This helper function is used to position a pointer @e offset bytes from the end
* of a buffer. */
static inline unsigned char *vectrpos(struct iovec *vec, size_t offset)
{
return (unsigned char *)vec->iov_base + (vec->iov_len - offset);
}
/** Calculate the size of current frame in bytes starting from vector and offset given */
static inline size_t get_size_from(const struct iovec *vects, int index, size_t offset, int all)
{
int i;
size_t total = 0;
if (index >= MAX_DATA_CHUNKS || offset > vects[index].iov_len) {
return 0;
}
for (i = index; i < MAX_DATA_CHUNKS; i++) {
if (i == CHUNK_REM && all == EXCLUDE_REM)
/* remainder should not be processed */
continue;
if (i == index)
total += vects[i].iov_len - offset;
else
total += vects[i].iov_len;
}
return total;
}
/** Return the number of bytes needed to align @e data_len to @e align_len boundary */
static inline size_t align_bytes_num(size_t data_len, size_t align_len)
{
size_t rem = data_len % align_len;
if (rem == 0)
return 0;
else
return align_len - rem;
}
/** Remap vectors pointing to various buffers with frame data to vectors used during frame alignment */
static void remap_vectors(camogm_state *state, struct iovec *chunks)
{
int chunk_index = 1;
if (state->exif > 0) {
chunks[CHUNK_LEADER].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_LEADER].iov_len = state->packetchunks[chunk_index++].bytes;
chunks[CHUNK_EXIF].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_EXIF].iov_len = state->packetchunks[chunk_index++].bytes;
chunks[CHUNK_HEADER].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_HEADER].iov_len = state->packetchunks[chunk_index++].bytes;
} else {
chunks[CHUNK_LEADER].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_LEADER].iov_len = JPEG_MARKER_LEN;
chunks[CHUNK_HEADER].iov_base = (void *)(state->packetchunks[chunk_index].chunk + JPEG_MARKER_LEN);
chunks[CHUNK_HEADER].iov_len = state->packetchunks[chunk_index++].bytes - JPEG_MARKER_LEN;
}
chunks[CHUNK_DATA_0].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_DATA_0].iov_len = state->packetchunks[chunk_index++].bytes;
if (state->writer_params.segments == 2) {
chunks[CHUNK_DATA_1].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_DATA_1].iov_len = state->packetchunks[chunk_index++].bytes;
}
chunks[CHUNK_TRAILER].iov_base = (void *)state->packetchunks[chunk_index].chunk;
chunks[CHUNK_TRAILER].iov_len = state->packetchunks[chunk_index].bytes;
/* some data may be left from previous frame, copy it to special buffer */
if (chunks[CHUNK_REM].iov_len != 0) {
vectcpy(&state->writer_params.prev_rem_vect, chunks[CHUNK_REM].iov_base, chunks[CHUNK_REM].iov_len);
vectshrink(&chunks[CHUNK_REM], chunks[CHUNK_REM].iov_len);
}
}
/** Debug function, checks frame alignment */
static int check_chunks(struct iovec *vects)
{
int i;
int ret = 0;
size_t sz = 0;
for (i = 0; i < MAX_DATA_CHUNKS; i++) {
if (i != CHUNK_REM) {
sz += vects[i].iov_len;
if ((vects[i].iov_len % ALIGNMENT_SIZE) != 0) {
dev_dbg(NULL, "ERROR: unaligned write from slot %d, length %u\n", i, vects[i].iov_len);
ret = -1;
}
}
dev_dbg(NULL, "chunk[%d]: ptr = %p, size = %d\n", i, vects[i].iov_base, vects[i].iov_len);
}
if ((sz % PHY_BLOCK_SIZE) != 0) {
dev_dbg(NULL, "ERROR: total length of the transaction is not aligned to sector boundary, total length %u\n", sz);
ret = -1;
} else {
dev_dbg(NULL, "===== frame is OK =====\n");
}
return ret;
}
/** Calculate the number of blocks this frame will occupy. The frame must be aligned to block size */
static size_t get_blocks_num(struct iovec *sgl, size_t n_elem)
{
int num;
size_t total = 0;
for (num = 0; num < n_elem; num++) {
total += sgl[num].iov_len;
}
return total / PHY_BLOCK_SIZE;
}
/** Allocate and initialize buffers for frame alignment */
int init_align_buffers(camogm_state *state)
{
state->writer_params.data_chunks = (struct iovec *)malloc(MAX_DATA_CHUNKS * sizeof(struct iovec));
if (state->writer_params.data_chunks == NULL) {
return -1;
}
state->writer_params.common_buff = (unsigned char *)malloc(COMMON_BUFF_SZ);
if (state->writer_params.common_buff == NULL) {
deinit_align_buffers(state);
return -1;
}
state->writer_params.rem_buff = (unsigned char *)malloc(REM_BUFF_SZ);
if (state->writer_params.rem_buff == NULL) {
deinit_align_buffers(state);
return -1;
}
state->writer_params.prev_rem_buff = (unsigned char *)malloc(REM_BUFF_SZ);
if (state->writer_params.prev_rem_buff == NULL) {
deinit_align_buffers(state);
return -1;
}
state->writer_params.data_chunks[CHUNK_COMMON].iov_base = (void *)state->writer_params.common_buff;
state->writer_params.data_chunks[CHUNK_COMMON].iov_len = 0;
state->writer_params.data_chunks[CHUNK_REM].iov_base = (void *)state->writer_params.rem_buff;
state->writer_params.data_chunks[CHUNK_REM].iov_len = 0;
state->writer_params.prev_rem_vect.iov_base = (void *)state->writer_params.prev_rem_buff;
state->writer_params.prev_rem_vect.iov_len = 0;
return 0;
}
/** Delete buffers for frame alignment */
void deinit_align_buffers(camogm_state *state)
{
struct writer_params *params = &state->writer_params;
if (params->data_chunks) {
free(params->data_chunks);
params->data_chunks = NULL;
}
if (params->common_buff) {
free(params->common_buff);
params->common_buff = NULL;
}
if (params->rem_buff) {
free(params->rem_buff);
params->rem_buff = NULL;
}
if (params->prev_rem_buff) {
free(params->prev_rem_buff);
params->prev_rem_buff = NULL;
}
}
/** Align current frame to disk sector boundary and each individual buffer to #ALIGNMENT_SIZE boundary */
void align_frame(camogm_state *state)
{
const char *dev = NULL;
unsigned char *src;
size_t len, total_sz, data_len;
struct iovec *chunks = state->writer_params.data_chunks;
struct iovec *cbuff = &chunks[CHUNK_COMMON];
struct iovec *rbuff = &state->writer_params.prev_rem_vect;
remap_vectors(state, chunks);
total_sz = get_size_from(chunks, 0, 0, INCLUDE_REM) + rbuff->iov_len;
if (total_sz < PHY_BLOCK_SIZE) {
/* the frame length is less than sector size, delay this frame */
if (rbuff->iov_len != 0) {
/* some data may be left from previous frame */
vectcpy(&chunks[CHUNK_REM], rbuff->iov_base, rbuff->iov_len);
vectshrink(rbuff, rbuff->iov_len);
}
dev_dbg(dev, "frame size is less than sector size: %u bytes; delay recording\n", total_sz);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_LEADER].iov_base, chunks[CHUNK_LEADER].iov_len);
vectshrink(&chunks[CHUNK_LEADER], chunks[CHUNK_LEADER].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_EXIF].iov_base, chunks[CHUNK_EXIF].iov_len);
vectshrink(&chunks[CHUNK_EXIF], chunks[CHUNK_EXIF].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_HEADER].iov_base, chunks[CHUNK_HEADER].iov_len);
vectshrink(&chunks[CHUNK_HEADER], chunks[CHUNK_HEADER].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_0].iov_base, chunks[CHUNK_DATA_0].iov_len);
vectshrink(&chunks[CHUNK_DATA_0], chunks[CHUNK_DATA_0].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_1].iov_base, chunks[CHUNK_DATA_1].iov_len);
vectshrink(&chunks[CHUNK_DATA_1], chunks[CHUNK_DATA_1].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
return;
}
/* copy remainder of previous frame to the beginning of common buffer */
if (rbuff->iov_len != 0) {
len = rbuff->iov_len;
dev_dbg(dev, "copy %u bytes from REM to common buffer\n", len);
vectcpy(cbuff, rbuff->iov_base, len);
vectshrink(rbuff, rbuff->iov_len);
}
/* copy JPEG marker */
len = chunks[CHUNK_LEADER].iov_len;
vectcpy(cbuff, chunks[CHUNK_LEADER].iov_base, len);
vectshrink(&chunks[CHUNK_LEADER], chunks[CHUNK_LEADER].iov_len);
/* copy Exif if present */
if (chunks[CHUNK_EXIF].iov_len != 0) {
len = chunks[CHUNK_EXIF].iov_len;
dev_dbg(dev, "copy %u bytes from EXIF to common buffer\n", len);
vectcpy(cbuff, chunks[CHUNK_EXIF].iov_base, len);
vectshrink(&chunks[CHUNK_EXIF], chunks[CHUNK_EXIF].iov_len);
}
/* align common buffer to ALIGNMENT boundary, APP15 marker should be placed before header data */
data_len = cbuff->iov_len + chunks[CHUNK_HEADER].iov_len;
len = align_bytes_num(data_len, ALIGNMENT_SIZE);
if (len < (JPEG_MARKER_LEN + JPEG_SIZE_LEN) && len != 0) {
/* the number of bytes needed for alignment is less than the length of the marker itself, increase the number of stuffing bytes */
len += ALIGNMENT_SIZE;
}
dev_dbg(dev, "total number of stuffing bytes in APP15 marker: %u\n", len);
app15[3] = len - JPEG_MARKER_LEN;
vectcpy(cbuff, app15, len);
/* copy JPEG header */
len = chunks[CHUNK_HEADER].iov_len;
dev_dbg(dev, "copy %u bytes from HEADER to common buffer\n", len);
vectcpy(cbuff, chunks[CHUNK_HEADER].iov_base, len);
vectshrink(&chunks[CHUNK_HEADER], chunks[CHUNK_HEADER].iov_len);
/* check if there is enough data to continue - JPEG data length can be too short */
len = get_size_from(chunks, CHUNK_DATA_0, 0, EXCLUDE_REM);
if (len < PHY_BLOCK_SIZE) {
size_t num = align_bytes_num(cbuff->iov_len, PHY_BLOCK_SIZE);
dev_dbg(dev, "jpeg data is too short, delay this frame\n");
if (len >= num) {
/* there is enough data to align common buffer to sector boundary */
if (num >= chunks[CHUNK_DATA_0].iov_len) {
vectcpy(cbuff, chunks[CHUNK_DATA_0].iov_base, chunks[CHUNK_DATA_0].iov_len);
num -= chunks[CHUNK_DATA_0].iov_len;
vectshrink(&chunks[CHUNK_DATA_0], chunks[CHUNK_DATA_0].iov_len);
} else {
src = vectrpos(&chunks[CHUNK_DATA_0], num);
vectcpy(cbuff, chunks[CHUNK_DATA_0].iov_base, num);
vectshrink(&chunks[CHUNK_DATA_0], num);
num = 0;
}
if (num >= chunks[CHUNK_DATA_1].iov_len) {
vectcpy(cbuff, chunks[CHUNK_DATA_1].iov_base, chunks[CHUNK_DATA_1].iov_len);
num -= chunks[CHUNK_DATA_1].iov_len;
vectshrink(&chunks[CHUNK_DATA_1], chunks[CHUNK_DATA_1].iov_len);
} else {
src = vectrpos(&chunks[CHUNK_DATA_1], num);
vectcpy(cbuff, chunks[CHUNK_DATA_1].iov_base, num);
vectshrink(&chunks[CHUNK_DATA_1], num);
num = 0;
}
if (num >= chunks[CHUNK_TRAILER].iov_len) {
vectcpy(cbuff, chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
num -= chunks[CHUNK_TRAILER].iov_len;
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
} else {
src = vectrpos(&chunks[CHUNK_TRAILER], num);
vectcpy(cbuff, chunks[CHUNK_TRAILER].iov_base, num);
vectshrink(&chunks[CHUNK_TRAILER], num);
num = 0;
}
} else {
/* there is not enough data to align common buffer to sector boundary, truncate common buffer */
data_len = cbuff->iov_len % PHY_BLOCK_SIZE;
src = vectrpos(cbuff, data_len);
vectcpy(&chunks[CHUNK_REM], src, data_len);
vectshrink(cbuff, data_len);
}
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_0].iov_base, chunks[CHUNK_DATA_0].iov_len);
vectshrink(&chunks[CHUNK_DATA_0], chunks[CHUNK_DATA_0].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_1].iov_base, chunks[CHUNK_DATA_1].iov_len);
vectshrink(&chunks[CHUNK_DATA_1], chunks[CHUNK_DATA_1].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
return;
}
/* align frame to sector size boundary; total size could have changed by the moment - recalculate */
total_sz = get_size_from(chunks, 0, 0, INCLUDE_REM);
len = total_sz % PHY_BLOCK_SIZE;
dev_dbg(dev, "number of bytes crossing sector boundary: %u\n", len);
if (len != 0) {
if (len >= (chunks[CHUNK_DATA_1].iov_len + chunks[CHUNK_TRAILER].iov_len)) {
/* current frame is not split or the second part of JPEG data is too short */
data_len = len - chunks[CHUNK_DATA_1].iov_len - chunks[CHUNK_TRAILER].iov_len;
src = vectrpos(&chunks[CHUNK_DATA_0], data_len);
vectcpy(&chunks[CHUNK_REM], src, data_len);
vectshrink(&chunks[CHUNK_DATA_0], data_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_1].iov_base, chunks[CHUNK_DATA_1].iov_len);
vectshrink(&chunks[CHUNK_DATA_1], chunks[CHUNK_DATA_1].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
} else if (len >= chunks[CHUNK_TRAILER].iov_len) {
/* there is enough data in second part to align the frame */
data_len = len - chunks[CHUNK_TRAILER].iov_len;
src = vectrpos(&chunks[CHUNK_DATA_1], data_len);
vectcpy(&chunks[CHUNK_REM], src, data_len);
vectshrink(&chunks[CHUNK_DATA_1], data_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
} else {
/* the trailing marker is split by sector boundary, copy (PHY_BLOCK_SIZE - 1) bytes from
* JPEG data block(s) to remainder buffer and then add trailing marker */
data_len = PHY_BLOCK_SIZE - (chunks[CHUNK_TRAILER].iov_len - len);
if (data_len >= chunks[CHUNK_DATA_1].iov_len) {
size_t cut_len = data_len - chunks[CHUNK_DATA_1].iov_len;
src = vectrpos(&chunks[CHUNK_DATA_0], cut_len);
vectcpy(&chunks[CHUNK_REM], src, cut_len);
vectshrink(&chunks[CHUNK_DATA_0], cut_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_DATA_1].iov_base, chunks[CHUNK_DATA_1].iov_len);
vectshrink(&chunks[CHUNK_DATA_1], chunks[CHUNK_DATA_1].iov_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
} else {
src = vectrpos(&chunks[CHUNK_DATA_1], data_len);
vectcpy(&chunks[CHUNK_REM], src, data_len);
vectshrink(&chunks[CHUNK_DATA_1], data_len);
vectcpy(&chunks[CHUNK_REM], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
}
}
} else {
/* the frame is aligned to sector boundary but some buffers may be not */
chunks[CHUNK_ALIGN].iov_base = vectrpos(cbuff, 0);
chunks[CHUNK_ALIGN].iov_len = 0;
if (chunks[CHUNK_DATA_1].iov_len == 0) {
data_len = chunks[CHUNK_DATA_0].iov_len % ALIGNMENT_SIZE;
src = vectrpos(&chunks[CHUNK_DATA_0], data_len);
vectcpy(&chunks[CHUNK_ALIGN], src, data_len);
vectshrink(&chunks[CHUNK_DATA_0], data_len);
} else {
data_len = chunks[CHUNK_DATA_1].iov_len % ALIGNMENT_SIZE;
src = vectrpos(&chunks[CHUNK_DATA_1], data_len);
vectcpy(&chunks[CHUNK_ALIGN], src, data_len);
vectshrink(&chunks[CHUNK_DATA_1], data_len);
}
vectcpy(&chunks[CHUNK_ALIGN], chunks[CHUNK_TRAILER].iov_base, chunks[CHUNK_TRAILER].iov_len);
vectshrink(&chunks[CHUNK_TRAILER], chunks[CHUNK_TRAILER].iov_len);
}
/* debug sanity check, should not happen */
if (cbuff->iov_len >= COMMON_BUFF_SZ) {
dev_dbg(NULL, "ERROR: the number of bytes copied to common buffer exceeds its size\n");
}
check_chunks(chunks);
}
/** Discard buffer pointers which makes the command slot marked as empty */
void reset_chunks(struct iovec *vects, int all)
{
int i;
for (i = 0; i < MAX_DATA_CHUNKS; i++) {
if (i != CHUNK_REM)
vects[i].iov_len = 0;
}
if (all) {
vects[CHUNK_REM].iov_len = 0;
}
}
/** Calculate and update LBA offsets, do not count remainder buffer. Return 1 if file position should be reset to the start */
int update_lba(camogm_state *state)
{
int ret = 0;
size_t total_sz;
struct iovec *chunks = state->writer_params.data_chunks;
total_sz = get_blocks_num(chunks, MAX_DATA_CHUNKS - 1);
if (state->writer_params.lba_current + total_sz <= state->writer_params.lba_end) {
state->writer_params.lba_current += total_sz;
} else {
state->writer_params.lba_current = state->writer_params.lba_start;
ret = 1;
}
return ret;
}
/** Go through all data buffers and pick only mapped ones excluding remainder buffer */
int get_data_buffers(camogm_state *state, struct iovec *mapped, size_t mapped_sz)
{
int ret = 0;
struct iovec *all = state->writer_params.data_chunks;
if (mapped_sz <= 0)
return ret;
for (int i = 0, j = 0; i < MAX_DATA_CHUNKS; i++) {
if (i != CHUNK_REM && all[i].iov_len != 0) {
if (j < mapped_sz) {
mapped[j++] = all[i];
ret = j;
} else {
ret = -1;
break;
}
}
}
return ret;
}
/** Prepare the last remaining block of data for recording, return the number of bytes ready for recording */
int prep_last_block(camogm_state *state)
{
int ret = 0;
size_t stuff_len;
unsigned char *src;
struct iovec *cvect = &state->writer_params.data_chunks[CHUNK_COMMON];
struct iovec *rvect = &state->writer_params.data_chunks[CHUNK_REM];
if (rvect->iov_len != 0) {
stuff_len = PHY_BLOCK_SIZE - rvect->iov_len;
src = vectrpos(rvect, 0);
memset(src, 0, stuff_len);
rvect->iov_len += stuff_len;
ret = rvect->iov_len;
vectcpy(cvect, rvect->iov_base, rvect->iov_len);
vectshrink(rvect, rvect->iov_len);
}
return ret;
}
/** Convert LBA to byte offset used for lseek */
off64_t lba_to_offset(uint64_t lba)
{
return lba * PHY_BLOCK_SIZE;
}
/** @file camogm_align.h
* @brief Provides frame alignment functions use for recording to block device.
* @copyright Copyright (C) 2017 Elphel, Inc.
*
* @par <b>License</b>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _CAMOGM_ALIGN_H
#define _CAMOGM_ALIGN_H
#include <unistd.h>
#include <sys/types.h>
#include "camogm.h"
#define PHY_BLOCK_SIZE 512 ///< Physical disk block size
#define JPEG_MARKER_LEN 2 ///< The size in bytes of JPEG marker
#define JPEG_SIZE_LEN 2 ///< The size in bytes of JPEG marker length field
#define INCLUDE_REM 1 ///< Include REM buffer to total size calculation
#define EXCLUDE_REM 0 ///< Exclude REM buffer from total size calculation
#define MAX_DATA_CHUNKS 9 ///< An array or JPEG frame chunks contains pointers to JPEG leading marker,
///< JPEG header, Exif data if present, stuffing bytes chunk which aligns
///< the frame size to disk sector boundary, JPEG data which
///< can be split into two chunks, align buffers, JPEG
///< trailing marker, and pointer to a buffer containing the remainder of a
///< frame. Nine chunks of data in total.
#define ALIGNMENT_SIZE 32 ///< Align buffers length to this amount of bytes
/** Common buffer should be large enough to contain JPEG header, Exif, some alignment bytes and remainder from previous frame */
#define COMMON_BUFF_SZ MAX_EXIF_SIZE + JPEG_HEADER_MAXSIZE + ALIGNMENT_SIZE + 2 * PHY_BLOCK_SIZE
#define REM_BUFF_SZ 2 * PHY_BLOCK_SIZE
///** This structure holds raw device buffer pointers */
//struct drv_pointers {
// uint64_t lba_start; ///< raw buffer starting LBA
// uint64_t lba_end; ///< raw buffer ending LBA
// uint64_t lba_write; ///< current write pointer inside raw buffer
// uint16_t wr_count; ///< the number of LBA to write next time
//};
/** Container structure for frame buffers */
//struct frame_buffers {
// struct fvec exif_buff; ///< Exif buffer
// struct fvec jpheader_buff; ///< JPEG header buffer
// struct fvec trailer_buff; ///< buffer for trailing marker
// struct fvec common_buff; ///< common buffer where other parts are combined
// struct fvec rem_buff; ///< remainder from previous frame
//};
/** Symbolic names for slots in buffer pointers. Buffer alignment function relies on the order of these names, so
* new names can be added but the overall order should not be changed */
enum {
CHUNK_LEADER, ///< pointer to JPEG leading marker
CHUNK_EXIF, ///< pointer to Exif buffer
CHUNK_HEADER, ///< pointer to JPEG header data excluding leading marker
CHUNK_COMMON, ///< pointer to common buffer
CHUNK_DATA_0, ///< pointer to JPEG data
CHUNK_DATA_1, ///< pointer to the second half of JPEG data if a frame crosses circbuf boundary
CHUNK_TRAILER, ///< pointer to JPEG trailing marker
CHUNK_ALIGN, ///< pointer to buffer where the second part of JPEG data should be aligned
CHUNK_REM ///< pointer to buffer containing the remainder of current frame. It will be recorded during next transaction
};
int init_align_buffers(camogm_state *state);
void deinit_align_buffers(camogm_state *state);
void align_frame(camogm_state *state);
void reset_chunks(struct iovec *vects, int all);
int update_lba(camogm_state *state);
int get_data_buffers(camogm_state *state, struct iovec *mapped, size_t all_sz);
int prep_last_block(camogm_state *state);
off64_t lba_to_offset(uint64_t lba);
#endif /* _CAMOGM_ALIGN_H */
......@@ -32,10 +32,16 @@
#include "camogm_jpeg.h"
#include "camogm_read.h"
#include "camogm_align.h"
/** State file record format. It includes device path in /dev, starting, current and ending LBAs */
#define STATE_FILE_FORMAT "%s\t%llu\t%llu\t%llu\n"
/* forward declarations */
static void *jpeg_writer(void *thread_args);
static int save_state_file(const rawdev_buffer *rawdev, uint64_t current_pos);
static int open_state_file(const rawdev_buffer *rawdev, uint64_t *current_pos);
/** Get starting and endign LBAs of the partition specified as raw device buffer */
static int get_disk_range(struct range *range)
{
......@@ -93,12 +99,12 @@ static int find_state(FILE *f, uint64_t *pos, const rawdev_buffer *rawdev)
}
/** Read state from file and restore disk write pointer */
static int open_state_file(const rawdev_buffer *rawdev)
static int open_state_file(const rawdev_buffer *rawdev, uint64_t *current_pos)
{
int fd, len;
FILE *f;
int ret = 0;
uint64_t curr_pos;
uint64_t lba_pos;
char buff[SMALL_BUFF_LEN] = {0};
if (strlen(rawdev->state_path) == 0) {
......@@ -107,15 +113,9 @@ static int open_state_file(const rawdev_buffer *rawdev)
f = fopen(rawdev->state_path, "r");
if (f != NULL) {
if (find_state(f, &curr_pos, rawdev) != -1) {
fd = open(SYSFS_AHCI_LBA_CURRENT, O_WRONLY);
if (fd >= 0) {
len = snprintf(buff, SMALL_BUFF_LEN, "%llu", curr_pos);
write(fd, buff, len + 1);
close(fd);
} else {
ret = -1;
}
if (find_state(f, &lba_pos, rawdev) != -1) {
*current_pos = lba_pos;
D0(fprintf(debug_file, "Got starting LBA from state file: %llu\n", lba_pos));
}
fclose(f);
} else {
......@@ -126,12 +126,11 @@ static int open_state_file(const rawdev_buffer *rawdev)
}
/** Save current position of the disk write pointer */
static int save_state_file(const rawdev_buffer *rawdev)
static int save_state_file(const rawdev_buffer *rawdev, uint64_t current_pos)
{
int ret = 0;
FILE *f;
struct range range;
uint64_t curr_pos;
if (strlen(rawdev->state_path) == 0) {
return ret;
......@@ -140,21 +139,13 @@ static int save_state_file(const rawdev_buffer *rawdev)
return -1;
}
// get raw device buffer current postion on disk, this position indicates where recording has stopped
f = fopen(SYSFS_AHCI_LBA_CURRENT, "r");
if (f == NULL) {
return -1;
}
fscanf(f, "%llu\n", &curr_pos);
fclose(f);
// save pointers to a regular file
f = fopen(rawdev->state_path, "w");
if (f == NULL) {
return -1;
}
fprintf(f, "Device\t\tStart LBA\tCurrent LBA\tEnd LBA\n");
fprintf(f, STATE_FILE_FORMAT, rawdev->rawdev_path, range.from, curr_pos, range.to);
fprintf(f, STATE_FILE_FORMAT, rawdev->rawdev_path, range.from, current_pos, range.to);
fflush(f);
fsync(fileno(f));
fclose(f);
......@@ -162,13 +153,70 @@ static int save_state_file(const rawdev_buffer *rawdev)
return ret;
}
/**
* @brief Initialize synchronization resources for disk writing thread and then start this thread. This function
* is call each time JPEG format is set or changed, thus we need to check the state of writing thread before
* initialization to prevent spawning multiple threads.
* @param[in] state a pointer to a structure containing current state
* @return 0 if initialization was successful and negative value otherwise
*/
int camogm_init_jpeg(camogm_state *state)
{
return 0;
int ret = 0;
int ret_val;
if (state->writer_params.state == STATE_STOPPED) {
ret_val = pthread_cond_init(&state->writer_params.main_cond, NULL);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not initialize conditional variable for main thread: %s\n", strerror(ret_val)));
ret = -1;
}
ret_val = pthread_cond_init(&state->writer_params.writer_cond, NULL);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not initialize conditional variable for writing thread: %s\n", strerror(ret_val)));
ret = -1;
}
ret_val = pthread_mutex_init(&state->writer_params.writer_mutex, NULL);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not initialize mutex for writing thread: %s\n", strerror(ret_val)));
ret = -1;
}
ret_val = pthread_create(&state->writer_params.writer_thread, NULL, jpeg_writer, (void *)state);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not start writer thread: %s\n", strerror(ret_val)));
ret = -1;
}
ret_val = init_align_buffers(state);
if (ret_val != 0) {
D0(fprintf(debug_file, "Can not initialize alignment buffers\n"));
ret = -1;
}
}
return ret;
}
void camogm_free_jpeg(void)
/**
* @brief Stop disk writing thread and free its resources. This function is called after the program receives 'exit' command.
* @param[in] state a pointer to a structure containing current state
* @return None
*/
void camogm_free_jpeg(camogm_state *state)
{
pthread_cond_destroy(&state->writer_params.main_cond);
pthread_cond_destroy(&state->writer_params.writer_cond);
pthread_mutex_destroy(&state->writer_params.writer_mutex);
// terminate writing thread
pthread_mutex_lock(&state->writer_params.writer_mutex);
state->writer_params.exit_thread = true;
pthread_cond_signal(&state->writer_params.writer_cond);
pthread_mutex_unlock(&state->writer_params.writer_mutex);
pthread_join(state->writer_params.writer_thread, NULL);
state->writer_params.exit_thread = false;
deinit_align_buffers(state);
}
/** Calculate the total length of current frame */
......@@ -195,6 +243,7 @@ int camogm_start_jpeg(camogm_state *state)
{
char * slash;
int rslt;
off64_t offset;
if (!state->rawdev_op) {
strcpy(state->path, state->path_prefix); // make state->path a directory name (will be replaced when the frames will be written)
......@@ -212,16 +261,18 @@ int camogm_start_jpeg(camogm_state *state)
}
}
} else {
if (open_state_file(&state->rawdev) != 0) {
D0(fprintf(debug_file, "Could not set write pointer via sysfs, recording will start from the beginning of partition: "
if (open_state_file(&state->rawdev, &state->writer_params.lba_current) != 0) {
D0(fprintf(debug_file, "Could not get write pointer from state file, recording will start from the beginning of partition: "
"%s\n", state->rawdev.rawdev_path));
}
state->rawdev.sysfs_fd = open(SYSFS_AHCI_WRITE, O_WRONLY);
D6(fprintf(debug_file, "Open sysfs file: %s\n", SYSFS_AHCI_WRITE));
if (state->rawdev.sysfs_fd < 0) {
D0(fprintf(debug_file, "Error opening sysfs file: %s\n", SYSFS_AHCI_WRITE));
state->writer_params.blockdev_fd = open(state->rawdev.rawdev_path, O_WRONLY);
if (state->writer_params.blockdev_fd < 0) {
D0(fprintf(debug_file, "Error opening block device: %s\n", state->rawdev.rawdev_path));
return -CAMOGM_FRAME_FILE_ERR;
}
offset = lba_to_offset(state->writer_params.lba_current - state->writer_params.lba_start);
lseek64(state->writer_params.blockdev_fd, offset, SEEK_SET);
D6(fprintf(debug_file, "Open block device: %s, offset in bytes: %llu\n", state->rawdev.rawdev_path, offset));
}
return 0;
......@@ -242,6 +293,7 @@ int camogm_frame_jpeg(camogm_state *state)
int port = state->port_num;
struct frame_data fdata = {0};
sprintf(state->path, "%s%d_%010ld_%06ld.jpeg", state->path_prefix, port, state->this_frame_params[port].timestamp_sec, state->this_frame_params[port].timestamp_usec);
if (!state->rawdev_op) {
l = 0;
for (i = 0; i < (state->chunk_index) - 1; i++) {
......@@ -249,7 +301,6 @@ int camogm_frame_jpeg(camogm_state *state)
chunks_iovec[i].iov_len = state->packetchunks[i + 1].bytes;
l += chunks_iovec[i].iov_len;
}
sprintf(state->path, "%s%d_%010ld_%06ld.jpeg", state->path_prefix, port, state->this_frame_params[port].timestamp_sec, state->this_frame_params[port].timestamp_usec);
if (((state->ivf = open(state->path, O_RDWR | O_CREAT, 0777))) < 0) {
D0(fprintf(debug_file, "Error opening %s for writing, returned %d, errno=%d\n", state->path, state->ivf, errno));
return -CAMOGM_FRAME_FILE_ERR;
......@@ -261,26 +312,37 @@ int camogm_frame_jpeg(camogm_state *state)
close(state->ivf);
return -CAMOGM_FRAME_FILE_ERR;
}
state->rawdev.last_jpeg_size = l;
close(state->ivf);
} else {
D6(fprintf(debug_file, "\ndump iovect array for port %u\n", state->port_num));
for (int i = 0; i < state->chunk_index - 1; i++) {
D6(fprintf(debug_file, "ptr: %p, length: %ld\n", state->packetchunks[i + 1].chunk, state->packetchunks[i + 1].bytes));
}
fdata.sensor_port = port;
fdata.cirbuf_ptr = state->cirbuf_rp[port];
fdata.jpeg_len = state->jpeg_len;
if (state->exif) {
fdata.meta_index = state->this_frame_params[port].meta_index;
fdata.cmd |= DRV_CMD_EXIF;
// next frame is ready for recording, signal this to the writer thread
pthread_mutex_lock(&state->writer_params.writer_mutex);
while (state->writer_params.data_ready)
pthread_cond_wait(&state->writer_params.main_cond, &state->writer_params.writer_mutex);
D6(fprintf(debug_file, "_13a_"));
D6(fprintf(debug_file, "\n"));
align_frame(state);
if (update_lba(state) == 1) {
D0(fprintf(debug_file, "The end of block device reached, continue recording from start\n"));
lseek(state->writer_params.blockdev_fd, 0, SEEK_SET);
}
fdata.cmd |= DRV_CMD_WRITE;
if (write(state->rawdev.sysfs_fd, &fdata, sizeof(struct frame_data)) < 0) {
D0(fprintf(debug_file, "Can not pass IO vector to driver: %s\n", strerror(errno)));
return -CAMOGM_FRAME_FILE_ERR;
D6(fprintf(debug_file, "Block device positions: start = %llu, current = %llu, end = %llu\n",
state->writer_params.lba_start, state->writer_params.lba_current, state->writer_params.lba_end));
// proceed if last frame was recorded without errors
if (state->writer_params.last_ret_val == 0) {
state->writer_params.data_ready = true;
pthread_cond_signal(&state->writer_params.writer_cond);
}
pthread_mutex_unlock(&state->writer_params.writer_mutex);
if (state->writer_params.last_ret_val != 0) {
return state->writer_params.last_ret_val;
}
// update statistics
state->rawdev.total_rec_len += camogm_get_jpeg_size(state);
}
return 0;
......@@ -296,19 +358,97 @@ int camogm_frame_jpeg(camogm_state *state)
int camogm_end_jpeg(camogm_state *state)
{
int ret = 0;
int bytes;
ssize_t iovlen;
struct frame_data fdata = {0};
if (state->rawdev_op) {
fdata.cmd = DRV_CMD_FINISH;
if (write(state->rawdev.sysfs_fd, &fdata, sizeof(struct frame_data)) < 0) {
D0(fprintf(debug_file, "Error sending 'finish' command to driver\n"));
// write any remaining data, do not use writer thread as there can be only one block left CHUNK_REM buffer
pthread_mutex_lock(&state->writer_params.writer_mutex);
bytes = prep_last_block(state);
if (bytes > 0) {
D6(fprintf(debug_file, "Write last block of data, size = %d\n", bytes));
// the remaining data block is placed in CHUNK_COMMON buffer, write just this buffer
iovlen = writev(state->writer_params.blockdev_fd, &state->writer_params.data_chunks[CHUNK_COMMON], 1);
if (iovlen < bytes) {
D0(fprintf(debug_file, "writev error: %s (returned %i, expected %i)\n", strerror(errno), iovlen, bytes));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
} else {
// update statistic, just one block written
state->writer_params.lba_current += 1;
state->rawdev.total_rec_len += bytes;
}
reset_chunks(state->writer_params.data_chunks, 1);
}
D6(fprintf(debug_file, "Closing sysfs file %s\n", SYSFS_AHCI_WRITE));
ret = close(state->rawdev.sysfs_fd);
pthread_mutex_unlock(&state->writer_params.writer_mutex);
D6(fprintf(debug_file, "Closing block device %s\n", state->rawdev.rawdev_path));
ret = close(state->writer_params.blockdev_fd);
if (ret == -1)
D0(fprintf(debug_file, "Error: %s\n", strerror(errno)));
save_state_file(&state->rawdev);
save_state_file(&state->rawdev, state->writer_params.lba_current);
}
return ret;
}
/**
* @brief Disk writing thread. This thread holds local copy of a structure containing current state
* of the program and updates it on a signal from main thread every time new frame is ready for recording.
* @param[in] thread_args a pointer to a structure containing current state
* @return None
*/
void *jpeg_writer(void *thread_args)
{
int rslt = 0;
int chunk_index;
ssize_t iovlen, l;
bool process = true;
struct iovec chunks_iovec[FILE_CHUNKS_NUM];
camogm_state *state = (camogm_state *)thread_args;
struct writer_params *params = &state->writer_params;
memset((void *)chunks_iovec, 0, sizeof(struct iovec) * FILE_CHUNKS_NUM);
pthread_mutex_lock(&params->writer_mutex);
params->state = STATE_RUNNING;
while (process) {
while (!params->data_ready && !params->exit_thread) {
pthread_cond_wait(&params->writer_cond, &params->writer_mutex);
}
if (params->exit_thread) {
process = false;
}
if (params->data_ready) {
l = 0;
state->writer_params.last_ret_val = 0;
chunk_index = get_data_buffers(state, &chunks_iovec, FILE_CHUNKS_NUM);
if (chunk_index > 0) {
for (int i = 0; i < chunk_index; i++)
l += chunks_iovec[i].iov_len;
iovlen = writev(state->writer_params.blockdev_fd, chunks_iovec, chunk_index);
if (iovlen < l) {
D0(fprintf(debug_file, "writev error: %s (returned %i, expected %i)\n", strerror(errno), iovlen, l));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
} else {
// update statistic
state->rawdev.last_jpeg_size = l;
state->rawdev.total_rec_len += state->rawdev.last_jpeg_size;
D6(fprintf(debug_file, "Current position in block device: %lld\n", lseek64(state->writer_params.blockdev_fd, 0, SEEK_CUR)));
}
} else {
D0(fprintf(debug_file, "data vector mapping error: %d)\n", chunk_index));
state->writer_params.last_ret_val = -CAMOGM_FRAME_FILE_ERR;
}
// release main thread
reset_chunks(state->writer_params.data_chunks, 0);
params->data_ready = false;
pthread_cond_signal(&params->main_cond);
}
}
params->state = STATE_STOPPED;
pthread_mutex_unlock(&state->writer_params.writer_mutex);
D5(fprintf(debug_file, "Exit from recording thread\n"));
return NULL;
}
......@@ -24,6 +24,6 @@ int camogm_init_jpeg(camogm_state *state);
int camogm_start_jpeg(camogm_state *state);
int camogm_frame_jpeg(camogm_state *state);
int camogm_end_jpeg(camogm_state *state);
void camogm_free_jpeg(void);
void camogm_free_jpeg(camogm_state *state);
#endif /* _CAMOGM_JPEG_H */
/** @brief This define is needed to use lseek64 and should be set before includes */
#define _LARGEFILE64_SOURCE
/** Needed for O_DIRECT */
#define _GNU_SOURCE
#include <stdbool.h>
#include <unistd.h>
......@@ -17,6 +19,7 @@
#include <ctype.h>
#include <signal.h>
#include <elphel/ahci_cmd.h>
#include <pthread.h>
#include "camogm.h"
#include "camogm_read.h"
......@@ -82,7 +85,16 @@ const char *circbufFileNames[] = {DEV393_PATH(DEV393_CIRCBUF0), DEV393_PATH(DEV3
unsigned long *ccam_dma_buf[SENSOR_PORTS];
int debug_level;
FILE* debug_file;
static volatile bool keep_running = true;
static volatile bool keep_running = true; // global flag for stopping threads
pthread_mutex_t writer_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t writer_block = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t writer_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t main_cond = PTHREAD_COND_INITIALIZER;
char block_dev[ELPHEL_PATH_MAX];
bool block_dev_op = true; // write data from writer thread instead of sending direct commands to driver
unsigned long *writer_data_ptr = NULL; // pointer to next data block in circbuf to be written from writer thread
int writer_data_len; // size of next data block to be written from writer thread
bool main_op = true; // allow main thread to proceed with data preparing
int open_files(camogm_state *state);
void clean_up(camogm_state *state);
......@@ -125,7 +137,7 @@ void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num)
memset(state, 0, sizeof(camogm_state));
camogm_reset(state); // sets state->buf_overruns =- 1
state->serialno = ipser[0];
debug_file = stderr;
debug_file = stdout;
camogm_debug_level(DEFAULT_DEBUG_LVL);
strcpy(state->debug_name, "stderr");
state->exif = DEFAULT_EXIF;
......@@ -187,7 +199,7 @@ int open_files(camogm_state *state)
clean_up(state);
return -3;
} else {
D0(fprintf(debug_file, "successfully mmap cirbuf region\n"));
D0(fprintf(debug_file, "successfully mmap cirbuf region, ptr = %p\n", (void *)ccam_dma_buf[port]));
}
return ret;
......@@ -207,7 +219,20 @@ int sendImageFrame(camogm_state *state)
fdata.cmd |= DRV_CMD_EXIF;
}
fdata.cmd |= DRV_CMD_WRITE_TEST;
ret = write(state->rawdev.sysfs_fd, &fdata, sizeof(struct frame_data));
if (!block_dev_op) {
ret = write(state->rawdev.sysfs_fd, &fdata, sizeof(struct frame_data));
} else {
// write to block device, but not using direct driver commands
pthread_mutex_lock(&writer_mutex);
while (!main_op)
pthread_cond_wait(&main_cond, &writer_mutex);
D0(fprintf(debug_file, "Update data pointer\n"));
writer_data_ptr = ccam_dma_buf[port] + state->cirbuf_rp[port];
writer_data_len = state->jpeg_len;
main_op = false;
pthread_cond_signal(&writer_cond);
pthread_mutex_unlock(&writer_mutex);
}
if (ret < 0) {
// D0(fprintf(debug_file, "Can not pass IO vector to driver (driver may be busy): %s\r", strerror(errno)));
ret = errno;
......@@ -668,7 +693,9 @@ int listener_loop(camogm_state *state, struct dd_params *dd_params)
seconds = difftime(now, dd_params->start_time);
if (seconds > 0)
wr_speed = mb_written / (unsigned long)seconds;
D0(fprintf(debug_file, "\r%lu MiB written, number of counts left: %04lu, average speed: %u MB/s",
// D0(fprintf(debug_file, "\r%lu MiB written, number of counts left: %04lu, average speed: %u MB/s",
// mb_written, dd_params->block_count, wr_speed));
D0(fprintf(debug_file, "%lu MiB written, number of counts left: %04lu, average speed: %u MB/s\n",
mb_written, dd_params->block_count, wr_speed));
read_stat(mb_written, state->path);
} else {
......@@ -749,6 +776,64 @@ static void start_test(camogm_state *state, const unsigned long max_cntr, const
close(fd);
}
// writer thread
void *writer(void *params)
{
int fd;
int ret = 0;
bool process = true;
unsigned char *tmp_buff;
size_t tmp_buff_sz = 1048576;
ret = posix_memalign((void **)&tmp_buff, sysconf(_SC_PAGE_SIZE), tmp_buff_sz);
if (ret == 0) {
printf("Aligned buffer allocated, ptr = %p\n", tmp_buff);
} else {
printf("Can not allocate aligned buffer\n");
return (void *)-1;
}
memset(tmp_buff, 1, tmp_buff_sz);
// open block device for writing
if ((fd = open(block_dev, O_WRONLY)) < 0) {
// if ((fd = open(block_dev, O_WRONLY | O_DIRECT)) < 0) {
// if ((fd = open("/mnt/sda1/thread_write.bin", O_CREAT | O_WRONLY)) < 0) {
// if ((fd = open("/mnt/sda1/thread_write.bin", O_CREAT | O_WRONLY | O_DIRECT)) < 0) {
printf("Can not open block device %s: %s\n", block_dev, strerror(errno));
return (void *)-1;
} else {
printf("Block device %s is opened from writer thread\n", block_dev);
}
// start recording
pthread_mutex_lock(&writer_mutex);
while (process && keep_running) {
while ((writer_data_ptr == NULL) && keep_running)
pthread_cond_wait(&writer_cond, &writer_mutex);
if (writer_data_ptr != NULL) {
printf("Emulate write, ptr = %p, size = %i\n", (void *)writer_data_ptr, writer_data_len);
// ret = write(fd, tmp_buff, tmp_buff_sz);
ret = write(fd, writer_data_ptr, writer_data_len);
if (ret < 0) {
printf("Error during recording: %s\n", strerror(errno));
process = false;
// exit from main thread also
keep_running = false;
}
printf("%i bytes written\n", ret);
writer_data_ptr = NULL; // wait while data will be ready
main_op = true;
pthread_cond_signal(&main_cond);
}
}
pthread_mutex_unlock(&writer_mutex);
// thread is terminating, clean up
close(fd);
printf("Exit from writer\n");
return NULL;
}
int main(int argc, char *argv[])
{
const char usage[] = "This program is intended for disk write tests\n" \
......@@ -780,6 +865,8 @@ int main(int argc, char *argv[])
struct dd_params dd_params = {0};
struct range range;
struct range disk_range;
pthread_t writer_thread;
pthread_attr_t attr;
if ((argc < 2) || (argv[1][1] == '-')) {
printf(usage, argv[0], argv[0]);
......@@ -790,6 +877,7 @@ int main(int argc, char *argv[])
case 'd':
// set path to disk under test
strncpy(disk_path, (const char *)optarg, ELPHEL_PATH_MAX - 1);
strncpy(block_dev, (const char *)optarg, ELPHEL_PATH_MAX - 1);
break;
case 'n':
strncpy(pipe_name_str, (const char *)optarg, ELPHEL_PATH_MAX - 1);
......@@ -846,6 +934,14 @@ int main(int argc, char *argv[])
camogm_init(&sstate, pipe_name_str, port_num);
if (!test_mode) {
// spawn writer thread first
pthread_attr_init(&attr);
ret = pthread_create(&writer_thread, &attr, writer, NULL);
if (ret < 0) {
printf("Can not spawn new thread: %s\n", strerror(errno));
return ret;
}
ret = open_files(&sstate);
if (ret < 0)
return ret;
......@@ -871,6 +967,12 @@ int main(int argc, char *argv[])
clean_up(&sstate);
ret = EXIT_FAILURE;
}
printf("Exit from main\n");
pthread_mutex_lock(&writer_mutex);
keep_running = false;
pthread_cond_signal(&writer_cond);
pthread_mutex_unlock(&writer_mutex);
pthread_join(writer_thread, NULL);
clean_up(&sstate);
} else {
if (dd_params.block_size != 0) {
......
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