diff --git a/driver/panda_stream.c b/driver/panda_stream.c index 626762d..e51353b 100644 --- a/driver/panda_stream.c +++ b/driver/panda_stream.c @@ -80,6 +80,8 @@ struct stream_open { bool stream_active; // If not set, any interrupts are unexpected uint32_t completion; // Copy of final interrupt status register struct timespec64 start_ts; // timestamp when PCAP becomes armed & enabled + bool start_ts_valid; // whether start_ts was set + spinlock_t start_ts_lock; // it handles race between isr and ioctl /* Reader status. */ int read_block_index; // Block being read @@ -201,8 +203,13 @@ static irqreturn_t stream_isr(int irq, void *dev_id) if (open->stream_active) { - if (IRQ_STATUS_START_EVENT(status)) + if (IRQ_STATUS_START_EVENT(status)) { + unsigned long flags; + spin_lock_irqsave(&open->start_ts_lock, flags); ktime_get_real_ts64(&open->start_ts); + open->start_ts_valid = true; + spin_unlock_irqrestore(&open->start_ts_lock, flags); + } if (IRQ_STATUS_NEW_BUFFER(status)) { @@ -297,8 +304,9 @@ static void start_hardware(struct stream_open *open) open->isr_block_index = 0; open->read_block_index = 0; open->read_offset = 0; - /* having a zeroed timestamp means the start event did not happen. */ open->start_ts = (const struct timespec64){0}; + open->start_ts_valid = false; + spin_lock_init(&open->start_ts_lock); /* After this point we can allow interrupts, they can potentially start as * soon as a DMA buffer is assigned. */ smp_wmb(); @@ -515,9 +523,20 @@ static long panda_stream_ioctl( case PANDA_COMPLETION: return stream_completion(open, (void __user *) arg); case PANDA_GET_START_TS: - return copy_to_user( - (void __user *) arg, &open->start_ts, - sizeof(struct timespec64)) ? -EIO : 0; + { + struct timespec64 ts; + bool ts_valid; + spin_lock(&open->start_ts_lock); + ts = open->start_ts; + ts_valid = open->start_ts_valid; + spin_unlock(&open->start_ts_lock); + if (!ts_valid) + return -EAGAIN; + + return copy_to_user( + (void __user *) arg, &ts, + sizeof(struct timespec64)) ? -EIO : 0; + } default: return -EINVAL; } diff --git a/server/data_server.c b/server/data_server.c index e5660e0..37bba82 100644 --- a/server/data_server.c +++ b/server/data_server.c @@ -113,10 +113,13 @@ static uint64_t experiment_sample_count; /* Save start timestamp, either from hardware (if that timestamp is not 0), or * from the driver (which was saved in the interrupt handler triggered by the * start event) */ -static void update_start_timestamp(void) +static bool update_start_timestamp(void) { struct timespec pcap_drv_start_ts, pcap_hw_start_ts; - hw_get_start_ts(&pcap_drv_start_ts); + bool valid_ts = hw_get_start_ts(&pcap_drv_start_ts); + if (!valid_ts) + return false; + hw_get_hw_start_ts(&pcap_hw_start_ts); if (pcap_hw_start_ts.tv_sec == 0 && pcap_hw_start_ts.tv_nsec == 0) { @@ -133,6 +136,8 @@ static void update_start_timestamp(void) pcap_hw_ts_offset_ns = drv_ts_num - hw_ts_num; pcap_hw_ts_offset_ns_valid = true; } + + return true; } @@ -154,18 +159,17 @@ static void capture_experiment(void) { void *block = get_write_block(data_buffer); size_t count; - do + do { count = hw_read_streamed_data(block, DATA_BLOCK_SIZE, &at_eof); - while (data_thread_running && count == 0 && !at_eof); - - /* Do our best to capture a timestamp before releasing the first block - * of the experiment. If the experiment is empty this return an empty - * timestamp, that's how it goes. */ - if (!ts_captured) - { - update_start_timestamp(); - ts_captured = true; - } + if (!ts_captured) + { + ts_captured = update_start_timestamp(); + /* we want to release the block when we get the timestamp to + * notify the readers */ + if (ts_captured) + break; + } + } while (data_thread_running && count == 0 && !at_eof); /* Unconditionally release the write block here. Either we have data to * send (exited loop above with count==0), or we're at the end of the diff --git a/server/hardware.c b/server/hardware.c index e0ebc38..73a23e7 100644 --- a/server/hardware.c +++ b/server/hardware.c @@ -316,22 +316,34 @@ unsigned int hw_read_streamed_completion(void) } -void hw_get_start_ts(struct timespec *ts) +bool hw_get_start_ts(struct timespec *ts) { - struct timespec64 compat_ts = (struct timespec64) {0}; - error_report(TEST_IO(ioctl(stream, PANDA_GET_START_TS, &compat_ts))); - ts->tv_sec = (time_t) compat_ts.tv_sec; - ts->tv_nsec = (typeof(ts->tv_nsec)) compat_ts.tv_nsec; + struct timespec64 compat_ts = {}; + if (ioctl(stream, PANDA_GET_START_TS, &compat_ts) == -1) + { + // EAGAIN indicates the timestamp hasn't been captured yet + if (errno != EAGAIN) + error_report(TEST_IO(-1)); + + return false; + } + else + { + ts->tv_sec = (time_t) compat_ts.tv_sec; + ts->tv_nsec = (typeof(ts->tv_nsec)) compat_ts.tv_nsec; + return true; + } } -void hw_get_hw_start_ts(struct timespec *ts) +bool hw_get_hw_start_ts(struct timespec *ts) { ts->tv_sec = (time_t) read_named_register(PCAP_TS_SEC); ts->tv_nsec = (typeof(ts->tv_nsec)) ((uint64_t) read_named_register(PCAP_TS_TICKS) * NSECS / hw_read_nominal_clock()); ts->tv_sec += ts->tv_nsec / NSECS; ts->tv_nsec = ts->tv_nsec % NSECS; + return true; } #endif diff --git a/server/hardware.h b/server/hardware.h index 2ee761b..82c84f9 100644 --- a/server/hardware.h +++ b/server/hardware.h @@ -165,9 +165,9 @@ unsigned int hw_read_streamed_completion(void); const char *hw_decode_completion(unsigned int completion); /* This function gets the timestamp when PCAP becomes armed and enabled */ -void hw_get_start_ts(struct timespec *ts); +bool hw_get_start_ts(struct timespec *ts); /* This one is latched in hardware instead of the driver */ -void hw_get_hw_start_ts(struct timespec *ts); +bool hw_get_hw_start_ts(struct timespec *ts); /* This function controls the arm/disarm state of data capture. Data capture is * armed by writing true with this function, after which hw_read_streamed_data() diff --git a/server/sim_hardware.c b/server/sim_hardware.c index 2450778..47ab1a4 100644 --- a/server/sim_hardware.c +++ b/server/sim_hardware.c @@ -182,16 +182,18 @@ void hw_write_arm_streamed_data(void) { } uint32_t hw_read_streamed_completion(void) { return 0; } -void hw_get_start_ts(struct timespec *ts) +bool hw_get_start_ts(struct timespec *ts) { clock_gettime(CLOCK_REALTIME, ts); + return true; } -void hw_get_hw_start_ts(struct timespec *ts) +bool hw_get_hw_start_ts(struct timespec *ts) { ts->tv_sec = 0; ts->tv_nsec = 0; + return true; }