Lessons from OpenBIC
Key design patterns and best practices learned from OpenBIC development.
Architecture Patterns
1. Platform Abstraction
flowchart TB
subgraph Common["Common Code"]
Core[Core Functions]
Protocol[Protocols]
Framework[Frameworks]
end
subgraph Platform["Platform Layer"]
P1[Platform A]
P2[Platform B]
P3[Platform C]
end
Common --> Platform
P1 --> HW1[Hardware A]
P2 --> HW2[Hardware B]
P3 --> HW3[Hardware C]
Implementation:
/* Common interface */
int platform_get_sensor_count(void);
struct sensor_cfg *platform_get_sensor_table(void);
/* Platform-specific implementation */
/* meta-facebook/yv35-cl/src/platform.c */
struct sensor_cfg sensor_config[] = { /* ... */ };
int platform_get_sensor_count(void)
{
return ARRAY_SIZE(sensor_config);
}
struct sensor_cfg *platform_get_sensor_table(void)
{
return sensor_config;
}
2. Table-Driven Design
Benefits:
- Easy to modify without code changes
- Clear data/logic separation
- Self-documenting configuration
/* GPIO pin table */
const struct gpio_cfg gpio_config[] = {
{ "FM_BMC_READY", GPIO_PORT_A, 5, GPIO_OUTPUT, GPIO_ACTIVE_HIGH },
{ "RST_RSMRST", GPIO_PORT_B, 2, GPIO_OUTPUT, GPIO_ACTIVE_LOW },
{ "PWRGD_CPU", GPIO_PORT_C, 7, GPIO_INPUT, GPIO_ACTIVE_HIGH },
/* ... */
};
/* FRU information table */
const struct fru_cfg fru_config[] = {
{ FRU_BMC, "BMC", fru_read_eeprom, 0x50 },
{ FRU_SYSTEM, "System", fru_read_eeprom, 0x51 },
{ FRU_CPU, "CPU", fru_read_smbios, 0 },
/* ... */
};
3. Service Layer Pattern
flowchart LR
subgraph API["External API"]
IPMI[IPMI]
PLDM[PLDM]
REST[REST]
end
subgraph Service["Service Layer"]
Power[Power Svc]
Sensor[Sensor Svc]
FW[FW Update Svc]
end
subgraph HAL["Hardware"]
GPIO[GPIO]
I2C[I2C]
Flash[Flash]
end
API --> Service
Service --> HAL
Benefits:
- Protocol independence
- Testability
- Reusability
Configuration Patterns
Kconfig Hierarchy
# Top-level feature config
config SENSOR_MONITOR
bool "Enable sensor monitoring"
default y
depends on I2C
if SENSOR_MONITOR
config SENSOR_POLL_INTERVAL_MS
int "Sensor poll interval (ms)"
default 1000
config SENSOR_THRESHOLD_CHECK
bool "Enable threshold checking"
default y
config SENSOR_EVENT_LOG
bool "Log sensor events"
depends on SENSOR_THRESHOLD_CHECK
default y
endif # SENSOR_MONITOR
Conditional Compilation
/* Feature-gated code */
#ifdef CONFIG_SENSOR_THRESHOLD_CHECK
check_thresholds(cfg, value);
#endif
#ifdef CONFIG_SENSOR_EVENT_LOG
log_sensor_event(&event);
#endif
Threading Patterns
Thread Priorities
| Thread | Priority | Purpose |
|---|---|---|
| ISR | - | Hardware interrupts |
| Watchdog | 1 | System health |
| Power Control | 3 | Critical operations |
| IPMI Handler | 5 | Protocol handling |
| Sensor Monitor | 7 | Background monitoring |
| Shell | 10 | Debug interface |
Work Queue Usage
/* Deferred work for non-critical tasks */
K_WORK_DEFINE(log_work, log_work_handler);
void isr_handler(void)
{
/* Quick ISR handling */
capture_data();
/* Defer heavy processing */
k_work_submit(&log_work);
}
void log_work_handler(struct k_work *work)
{
/* Safe to do blocking operations */
write_to_flash();
send_event_notification();
}
Error Handling Patterns
Return Code Convention
/* Consistent error codes */
enum bic_status {
BIC_OK = 0,
BIC_ERR_INVALID_PARAM = -1,
BIC_ERR_TIMEOUT = -2,
BIC_ERR_BUSY = -3,
BIC_ERR_NOT_FOUND = -4,
BIC_ERR_IO = -5,
BIC_ERR_NO_MEM = -6,
};
/* Error propagation */
int higher_level_func(void)
{
int ret = lower_level_func();
if (ret != BIC_OK) {
LOG_ERR("Lower level failed: %d", ret);
return ret; /* Propagate error */
}
return BIC_OK;
}
Retry Pattern
int reliable_read(struct sensor_cfg *cfg, int *value)
{
int retry = 3;
int ret;
while (retry-- > 0) {
ret = cfg->read(cfg, value);
if (ret == BIC_OK) {
return BIC_OK;
}
LOG_WRN("Read failed, retrying (%d left)", retry);
k_msleep(10);
}
LOG_ERR("Read failed after retries");
return BIC_ERR_IO;
}
Testing Patterns
Unit Testable Design
/* Dependency injection for testing */
struct sensor_ops {
int (*read)(uint8_t addr, uint8_t reg, uint8_t *data);
int (*write)(uint8_t addr, uint8_t reg, uint8_t data);
};
/* Production implementation */
static const struct sensor_ops hw_ops = {
.read = i2c_read_byte,
.write = i2c_write_byte,
};
/* Test mock */
static const struct sensor_ops mock_ops = {
.read = mock_read,
.write = mock_write,
};
int sensor_init(const struct sensor_ops *ops);
Best Practices Summary
- Separate Common and Platform Code - Maximize reuse
- Use Tables for Configuration - Data-driven design
- Layer Your Architecture - Clear responsibilities
- Consistent Error Handling - Propagate and log errors
- Design for Testability - Dependency injection
- Document with Kconfig - Self-describing features
- Use Appropriate Thread Priorities - Balance responsiveness
- Defer Non-Critical Work - Keep ISRs fast
Next Steps
Learn about ASPEED RoT for security patterns.