Skip to content

Commit 7fdbf1e

Browse files
committed
Update Pi Model detection; allow degraded non-root operation.
o Update PI model detection. Now detects Raspberry Pi 4 (fixes #840) and Rasberry Pi 3 A+ (fixes #727) o If we can't mmap /dev/mem, fall back to /dev/gpiomem. Unfortunately, this will not allow to read the 1Mhz timer nor allow for PWM to work properly (fixes #680)
1 parent 5931e41 commit 7fdbf1e

File tree

3 files changed

+133
-53
lines changed

3 files changed

+133
-53
lines changed

include/gpio.h

+4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ class PinPulser {
113113
virtual void WaitPulseFinished() {}
114114
};
115115

116+
// Get rolling over microsecond counter. We get this from a hardware register
117+
// if possible and a terrible slow fallback otherwise.
118+
uint32_t GetMicrosecondCounter();
119+
116120
} // end namespace rgb_matrix
117121

118122
#endif // RPI_GPIO_H

lib/gpio.cc

+129-48
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
// Raspberry 1 and 2 have different base addresses for the periphery
7070
#define BCM2708_PERI_BASE 0x20000000
7171
#define BCM2709_PERI_BASE 0x3F000000
72+
#define BCM2711_PERI_BASE 0xFE000000
7273

7374
#define GPIO_REGISTER_OFFSET 0x200000
7475
#define COUNTER_1Mhz_REGISTER_OFFSET 0x3000
@@ -198,43 +199,107 @@ uint32_t GPIO::RequestInputs(uint32_t inputs) {
198199
return inputs;
199200
}
200201

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
206214
buffer[r >= 0 ? r : 0] = '\0';
207215
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;
214235
}
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;
216260
}
217261

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;
221264
}
222265

223266
static uint32_t JitterAllowanceMicroseconds() {
224267
// If this is a Raspberry Pi2 or 3, we can allow to burn a bit more busy-wait
225268
// CPU cycles to get the timing accurate as we have more CPU to spare.
226269
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);
228271
return allowance_us;
229272
}
230273

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+
}
233281

234282
int mem_fd;
235283
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;
238303
}
239304

240305
uint32_t *result =
@@ -249,8 +314,8 @@ static uint32_t *mmap_bcm_register(bool isRPi2, off_t register_offset) {
249314

250315
if (result == MAP_FAILED) {
251316
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);
254319
return NULL;
255320
}
256321
return result;
@@ -259,27 +324,22 @@ static uint32_t *mmap_bcm_register(bool isRPi2, off_t register_offset) {
259324
static bool mmap_all_bcm_registers_once() {
260325
if (s_GPIO_registers != NULL) return true; // alrady done.
261326

262-
const bool isPI2 = IsRaspberryPi2();
263-
264327
// 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);
266329
if (s_GPIO_registers == NULL) {
267330
return false;
268331
}
269332

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;
274337
}
275-
s_Timer1Mhz = timereg + 1;
276338

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+
283343
return true;
284344
}
285345

@@ -360,7 +420,7 @@ static void (*busy_sleep_impl)(long) = sleep_nanos_rpi_1;
360420
// our RT-thread is locked onto one of these.
361421
// So let's tell it not to do that.
362422
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.
364424
const int out = open("/proc/sys/kernel/sched_rt_runtime_us", O_WRONLY);
365425
if (out < 0) return;
366426
write(out, "-1", 2);
@@ -370,9 +430,15 @@ static void DisableRealtimeThrottling() {
370430
bool Timers::Init() {
371431
if (!mmap_all_bcm_registers_once())
372432
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();
376442
return true;
377443
}
378444

@@ -389,14 +455,15 @@ void Timers::sleep_nanos(long nanos) {
389455
//
390456
// We use the global 1Mhz hardware timer to measure the actual time period
391457
// 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.
393460
static long kJitterAllowanceNanos = JitterAllowanceMicroseconds() * 1000;
394461
if (nanos > kJitterAllowanceNanos + 5000) {
395-
const uint32_t before = *s_Timer1Mhz;
462+
const uint32_t before = GetMicrosecondCounter();
396463
struct timespec sleep_time
397464
= { 0, nanos - kJitterAllowanceNanos };
398465
nanosleep(&sleep_time, NULL);
399-
const uint32_t after = *s_Timer1Mhz;
466+
const uint32_t after = GetMicrosecondCounter();
400467
const long nanoseconds_passed = 1000 * (uint32_t)(after - before);
401468
if (nanoseconds_passed > nanos) {
402469
return; // darn, missed it.
@@ -452,7 +519,13 @@ class HardwarePinPulser : public PinPulser {
452519
#ifdef DISABLE_HARDWARE_PULSES
453520
return false;
454521
#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;
456529
#endif
457530
}
458531

@@ -541,7 +614,7 @@ class HardwarePinPulser : public PinPulser {
541614
*fifo_ = 0;
542615

543616
sleep_hint_ = sleep_hints_[c];
544-
start_time_ = *s_Timer1Mhz;
617+
start_time_ = GetMicrosecondCounter();
545618
triggered_ = true;
546619
s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_PWEN1 | PWM_CTL_POLA1;
547620
}
@@ -555,7 +628,7 @@ class HardwarePinPulser : public PinPulser {
555628
// the hardware once it is done with the pulse. Sounds silly that there is
556629
// not.
557630
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_;
559632
const int to_sleep = sleep_hint_ - already_elapsed_usec;
560633
if (to_sleep > 0) {
561634
struct timespec sleep_time = { 0, 1000 * to_sleep };
@@ -565,7 +638,7 @@ class HardwarePinPulser : public PinPulser {
565638
{
566639
// Record histogram of realtime jitter how much longer we actually
567640
// took.
568-
const int total_us = *timer1Mhz - start_time_;
641+
const int total_us = GetMicrosecondCounter() - start_time_;
569642
const int nanoslept = total_us - already_elapsed_usec;
570643
int overshoot = nanoslept - (to_sleep + JitterAllowanceMicroseconds());
571644
if (overshoot < 0) overshoot = 0;
@@ -634,7 +707,15 @@ PinPulser *PinPulser::Create(GPIO *io, uint32_t gpio_mask,
634707
}
635708

636709
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;
638719
}
639720

640721
} // namespace rgb_matrix

lib/led-matrix.cc

-5
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,6 @@
4747
#endif
4848

4949
namespace rgb_matrix {
50-
51-
// Get rolling over microsecond counter. Right now for experimental
52-
// purposes declared here (defined in gpio.cc).
53-
uint32_t GetMicrosecondCounter();
54-
5550
using namespace internal;
5651

5752
// Pump pixels to screen. Needs to be high priority real-time because jitter

0 commit comments

Comments
 (0)