69
69
// Raspberry 1 and 2 have different base addresses for the periphery
70
70
#define BCM2708_PERI_BASE 0x20000000
71
71
#define BCM2709_PERI_BASE 0x3F000000
72
+ #define BCM2711_PERI_BASE 0xFE000000
72
73
73
74
#define GPIO_REGISTER_OFFSET 0x200000
74
75
#define COUNTER_1Mhz_REGISTER_OFFSET 0x3000
@@ -198,43 +199,107 @@ uint32_t GPIO::RequestInputs(uint32_t inputs) {
198
199
return inputs;
199
200
}
200
201
201
- static bool DetermineIsRaspberryPi2 () {
202
- // TODO: there must be a better, more robust way. Can we ask the processor ?
203
- char buffer[2048 ];
204
- const int fd = open (" /proc/cmdline" , O_RDONLY);
205
- ssize_t r = read (fd, buffer, sizeof (buffer) - 1 ); // returns all in one read.
202
+ // We are not interested in the _exact_ model, just good enough to determine
203
+ // What to do.
204
+ enum class RaspberryPiModel {
205
+ PI_MODEL_1,
206
+ PI_MODEL_2_3,
207
+ PI_MODEL_4
208
+ };
209
+
210
+ static int ReadFileToBuffer (char *buffer, size_t size, const char *filename) {
211
+ const int fd = open (filename, O_RDONLY);
212
+ if (fd < 0 ) return -1 ;
213
+ ssize_t r = read (fd, buffer, size - 1 ); // assume one read enough
206
214
buffer[r >= 0 ? r : 0 ] = ' \0 ' ;
207
215
close (fd);
208
- const char *mem_size_key;
209
- uint64_t mem_size = 0 ;
210
- if ((mem_size_key = strstr (buffer, " mem_size=" )) != NULL
211
- && (sscanf (mem_size_key + strlen (" mem_size=" ), " %" PRIx64, &mem_size) == 1 )
212
- && (mem_size >= 0x3F000000 )) {
213
- return true ;
216
+ return r;
217
+ }
218
+
219
+ static RaspberryPiModel DetermineRaspberryModel () {
220
+ char buffer[4096 ];
221
+ if (ReadFileToBuffer (buffer, sizeof (buffer), " /proc/cpuinfo" ) < 0 ) {
222
+ fprintf (stderr, " Reading cpuinfo: Could not determine Pi model\n " );
223
+ return RaspberryPiModel::PI_MODEL_2_3; // safe guess fallback.
224
+ }
225
+ static const char RevisionTag[] = " Revision" ;
226
+ const char *revision_key;
227
+ if ((revision_key = strstr (buffer, RevisionTag)) == NULL ) {
228
+ fprintf (stderr, " non-existent Revision: Could not determine Pi model\n " );
229
+ return RaspberryPiModel::PI_MODEL_2_3;
230
+ }
231
+ unsigned int pi_revision;
232
+ if (sscanf (index (revision_key, ' :' ) + 1 , " %x" , &pi_revision) != 1 ) {
233
+ fprintf (stderr, " Unknown Revision: Could not determine Pi model\n " );
234
+ return RaspberryPiModel::PI_MODEL_2_3;
214
235
}
215
- return false ;
236
+
237
+ const unsigned pi_type = (pi_revision >> 4 ) & 0xff ;
238
+ switch (pi_type) {
239
+ case 0x00 : /* A */
240
+ case 0x01 : /* B, Compute Module 1 */
241
+ case 0x02 : /* A+ */
242
+ case 0x03 : /* B+ */
243
+ case 0x05 : /* Alpha */
244
+ case 0x06 : /* Compute Module */
245
+ case 0x09 : /* Zero */
246
+ case 0x0c : /* Zero W */
247
+ return RaspberryPiModel::PI_MODEL_1;
248
+
249
+ case 0x11 : /* Pi 4 */
250
+ return RaspberryPiModel::PI_MODEL_4;
251
+
252
+ default : /* a bunch of versions represneting Pi 2 or Pi 3 */
253
+ return RaspberryPiModel::PI_MODEL_2_3;
254
+ }
255
+ }
256
+
257
+ static RaspberryPiModel GetPiModel () {
258
+ static RaspberryPiModel pi_model = DetermineRaspberryModel ();
259
+ return pi_model;
216
260
}
217
261
218
- static bool IsRaspberryPi2 () {
219
- static bool ispi2 = DetermineIsRaspberryPi2 ();
220
- return ispi2;
262
+ static int GetNumCores () {
263
+ return GetPiModel () == RaspberryPiModel::PI_MODEL_1 ? 1 : 4 ;
221
264
}
222
265
223
266
static uint32_t JitterAllowanceMicroseconds () {
224
267
// If this is a Raspberry Pi2 or 3, we can allow to burn a bit more busy-wait
225
268
// CPU cycles to get the timing accurate as we have more CPU to spare.
226
269
static int allowance_us = EMPIRICAL_NANOSLEEP_OVERHEAD_US
227
- + (IsRaspberryPi2 () ? EMPIRICAL_NANOSLEEP_EXTRA_OVERHEAD_US : 0 );
270
+ + (GetNumCores () > 1 ? EMPIRICAL_NANOSLEEP_EXTRA_OVERHEAD_US : 0 );
228
271
return allowance_us;
229
272
}
230
273
231
- static uint32_t *mmap_bcm_register (bool isRPi2, off_t register_offset) {
232
- const off_t base = (isRPi2 ? BCM2709_PERI_BASE : BCM2708_PERI_BASE);
274
+ static uint32_t *mmap_bcm_register (off_t register_offset) {
275
+ off_t base = BCM2709_PERI_BASE; // safe fallback guess.
276
+ switch (GetPiModel ()) {
277
+ case RaspberryPiModel::PI_MODEL_1: base = BCM2708_PERI_BASE; break ;
278
+ case RaspberryPiModel::PI_MODEL_2_3: base = BCM2709_PERI_BASE; break ;
279
+ case RaspberryPiModel::PI_MODEL_4: base = BCM2711_PERI_BASE; break ;
280
+ }
233
281
234
282
int mem_fd;
235
283
if ((mem_fd = open (" /dev/mem" , O_RDWR|O_SYNC) ) < 0 ) {
236
- perror (" can't open /dev/mem: " );
237
- return NULL ;
284
+ // Try to fall back to /dev/gpiomem.
285
+
286
+ // Unfortunately, this will not work with all the registers we want
287
+ // to map, so we'll get degraded performance.
288
+ // Annoyingly, it will still successfully mmap(), but simply not work
289
+ // later :/
290
+ // So catch these cases here and return early with failure.
291
+ // TODO: send patch to kernel people to include these ranges.
292
+
293
+ // PWM subsystem can not be accessed by gpiomem
294
+ if (register_offset == GPIO_PWM_BASE_OFFSET)
295
+ return NULL ;
296
+
297
+ // Also, we can't read the 1Mhz counter with gpiomem.
298
+ if (register_offset == COUNTER_1Mhz_REGISTER_OFFSET)
299
+ return NULL ;
300
+
301
+ mem_fd = open (" /dev/gpiomem" , O_RDWR|O_SYNC);
302
+ if (mem_fd < 0 ) return NULL ;
238
303
}
239
304
240
305
uint32_t *result =
@@ -249,8 +314,8 @@ static uint32_t *mmap_bcm_register(bool isRPi2, off_t register_offset) {
249
314
250
315
if (result == MAP_FAILED) {
251
316
perror (" mmap error: " );
252
- fprintf (stderr, " %s: MMapping from base 0x%lx, offset 0x%lx\n " ,
253
- isRPi2 ? " RPi2,3 " : " RPi1 " , base, register_offset);
317
+ fprintf (stderr, " MMapping from base 0x%lx, offset 0x%lx\n " ,
318
+ base, register_offset);
254
319
return NULL ;
255
320
}
256
321
return result;
@@ -259,27 +324,22 @@ static uint32_t *mmap_bcm_register(bool isRPi2, off_t register_offset) {
259
324
static bool mmap_all_bcm_registers_once () {
260
325
if (s_GPIO_registers != NULL ) return true ; // alrady done.
261
326
262
- const bool isPI2 = IsRaspberryPi2 ();
263
-
264
327
// The common GPIO registers.
265
- s_GPIO_registers = mmap_bcm_register (isPI2, GPIO_REGISTER_OFFSET);
328
+ s_GPIO_registers = mmap_bcm_register (GPIO_REGISTER_OFFSET);
266
329
if (s_GPIO_registers == NULL ) {
267
330
return false ;
268
331
}
269
332
270
- // Time measurement.
271
- uint32_t *timereg = mmap_bcm_register (isPI2, COUNTER_1Mhz_REGISTER_OFFSET);
272
- if (timereg = = NULL ) {
273
- return false ;
333
+ // Time measurement. Might fail when run as non-root.
334
+ uint32_t *timereg = mmap_bcm_register (COUNTER_1Mhz_REGISTER_OFFSET);
335
+ if (timereg ! = NULL ) {
336
+ s_Timer1Mhz = timereg + 1 ;
274
337
}
275
- s_Timer1Mhz = timereg + 1 ;
276
338
277
- // Hardware pin-pulser.
278
- s_PWM_registers = mmap_bcm_register (isPI2, GPIO_PWM_BASE_OFFSET);
279
- s_CLK_registers = mmap_bcm_register (isPI2, GPIO_CLK_BASE_OFFSET);
280
- if (!s_PWM_registers || !s_CLK_registers) {
281
- return false ;
282
- }
339
+ // Hardware pin-pulser. Might fail when run as non-root.
340
+ s_PWM_registers = mmap_bcm_register (GPIO_PWM_BASE_OFFSET);
341
+ s_CLK_registers = mmap_bcm_register (GPIO_CLK_BASE_OFFSET);
342
+
283
343
return true ;
284
344
}
285
345
@@ -360,7 +420,7 @@ static void (*busy_sleep_impl)(long) = sleep_nanos_rpi_1;
360
420
// our RT-thread is locked onto one of these.
361
421
// So let's tell it not to do that.
362
422
static void DisableRealtimeThrottling () {
363
- if (! IsRaspberryPi2 () ) return ; // Not safe if we don't have > 1 core.
423
+ if (GetNumCores () == 1 ) return ; // Not safe if we don't have > 1 core.
364
424
const int out = open (" /proc/sys/kernel/sched_rt_runtime_us" , O_WRONLY);
365
425
if (out < 0 ) return ;
366
426
write (out, " -1" , 2 );
@@ -370,9 +430,15 @@ static void DisableRealtimeThrottling() {
370
430
bool Timers::Init () {
371
431
if (!mmap_all_bcm_registers_once ())
372
432
return false ;
373
- const bool isRPi2 = IsRaspberryPi2 ();
374
- busy_sleep_impl = isRPi2 ? sleep_nanos_rpi_2 : sleep_nanos_rpi_1;
375
- if (isRPi2) DisableRealtimeThrottling ();
433
+ switch (GetPiModel ()) {
434
+ case RaspberryPiModel::PI_MODEL_1:
435
+ busy_sleep_impl = sleep_nanos_rpi_1;
436
+ break ;
437
+ default :
438
+ // TODO: re-determine timings in current operating systems.
439
+ busy_sleep_impl = sleep_nanos_rpi_2;
440
+ }
441
+ DisableRealtimeThrottling ();
376
442
return true ;
377
443
}
378
444
@@ -389,14 +455,15 @@ void Timers::sleep_nanos(long nanos) {
389
455
//
390
456
// We use the global 1Mhz hardware timer to measure the actual time period
391
457
// that has passed, and then inch forward for the remaining time with
392
- // busy wait.
458
+ // busy wait. TODO: if someone runs this as non-root, the hardware timer
459
+ // is not available.
393
460
static long kJitterAllowanceNanos = JitterAllowanceMicroseconds () * 1000 ;
394
461
if (nanos > kJitterAllowanceNanos + 5000 ) {
395
- const uint32_t before = *s_Timer1Mhz ;
462
+ const uint32_t before = GetMicrosecondCounter () ;
396
463
struct timespec sleep_time
397
464
= { 0 , nanos - kJitterAllowanceNanos };
398
465
nanosleep (&sleep_time, NULL );
399
- const uint32_t after = *s_Timer1Mhz ;
466
+ const uint32_t after = GetMicrosecondCounter () ;
400
467
const long nanoseconds_passed = 1000 * (uint32_t )(after - before);
401
468
if (nanoseconds_passed > nanos) {
402
469
return ; // darn, missed it.
@@ -452,7 +519,13 @@ class HardwarePinPulser : public PinPulser {
452
519
#ifdef DISABLE_HARDWARE_PULSES
453
520
return false ;
454
521
#else
455
- return gpio_mask == (1 << 18 ) || gpio_mask == (1 << 12 );
522
+ const bool can_handle = gpio_mask == (1 << 18 ) || gpio_mask == (1 << 12 );
523
+ if (can_handle && (s_PWM_registers == NULL || s_CLK_registers == NULL )) {
524
+ fprintf (stderr, " Flicker alert: you have to run as root to use improved "
525
+ " PWM with hardware pulse.\n " );
526
+ return false ;
527
+ }
528
+ return can_handle;
456
529
#endif
457
530
}
458
531
@@ -541,7 +614,7 @@ class HardwarePinPulser : public PinPulser {
541
614
*fifo_ = 0 ;
542
615
543
616
sleep_hint_ = sleep_hints_[c];
544
- start_time_ = *s_Timer1Mhz ;
617
+ start_time_ = GetMicrosecondCounter () ;
545
618
triggered_ = true ;
546
619
s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_PWEN1 | PWM_CTL_POLA1;
547
620
}
@@ -555,7 +628,7 @@ class HardwarePinPulser : public PinPulser {
555
628
// the hardware once it is done with the pulse. Sounds silly that there is
556
629
// not.
557
630
if (sleep_hint_ > 0 ) {
558
- const uint32_t already_elapsed_usec = *s_Timer1Mhz - start_time_;
631
+ const uint32_t already_elapsed_usec = GetMicrosecondCounter () - start_time_;
559
632
const int to_sleep = sleep_hint_ - already_elapsed_usec;
560
633
if (to_sleep > 0 ) {
561
634
struct timespec sleep_time = { 0 , 1000 * to_sleep };
@@ -565,7 +638,7 @@ class HardwarePinPulser : public PinPulser {
565
638
{
566
639
// Record histogram of realtime jitter how much longer we actually
567
640
// took.
568
- const int total_us = *timer1Mhz - start_time_;
641
+ const int total_us = GetMicrosecondCounter () - start_time_;
569
642
const int nanoslept = total_us - already_elapsed_usec;
570
643
int overshoot = nanoslept - (to_sleep + JitterAllowanceMicroseconds ());
571
644
if (overshoot < 0 ) overshoot = 0 ;
@@ -634,7 +707,15 @@ PinPulser *PinPulser::Create(GPIO *io, uint32_t gpio_mask,
634
707
}
635
708
636
709
uint32_t GetMicrosecondCounter () {
637
- return s_Timer1Mhz ? *s_Timer1Mhz : 0 ;
710
+ if (s_Timer1Mhz) return *s_Timer1Mhz;
711
+
712
+ // When run as non-root, we can't read the timer. Fall back to slow
713
+ // operating-system ways.
714
+ struct timespec ts;
715
+ clock_gettime (CLOCK_MONOTONIC, &ts);
716
+ const uint64_t micros = ts.tv_nsec / 1000 ;
717
+ const uint64_t epoch_usec = (uint64_t )ts.tv_sec * 1000000 + micros;
718
+ return epoch_usec & 0xFFFFFFFF ;
638
719
}
639
720
640
721
} // namespace rgb_matrix
0 commit comments