Skip to content

Commit

Permalink
pstore: persistent storage for Linux
Browse files Browse the repository at this point in the history
Integrate into Linux pstore system to store kernel
crash logs in EMC memory.
  • Loading branch information
surban committed Jun 28, 2024
1 parent 1e53f4d commit f46bd16
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The following features are implemented:
- battery charger (BQ25713)
- external power supply with USB PD (STUSB4500) and USB charger detector (MAX14636)
- charging mode with system power off
- Linux pstore (persistent storage) integration for kernel panic logs
- configuration storage in flash memory
- full devicetree integration

Expand Down
7 changes: 7 additions & 0 deletions openemc-bootloader/emc-bootloader.x
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,12 @@ SECTIONS
KEEP(*(.defmt_log .defmt_log.*));
. = ALIGN(8);
} > RAM

/* Pstore buffer. */
.pstore (NOLOAD) : ALIGN(8)
{
KEEP(*(.pstore .pstore.*));
. = ALIGN(8);
} > RAM
}
INSERT BEFORE .data;
6 changes: 6 additions & 0 deletions openemc-bootloader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ pub static mut BOOTLOADER_LOG: core::mem::MaybeUninit<
pub static mut LOG: core::mem::MaybeUninit<defmt_ringbuf::RingBuffer<{ openemc_shared::LOG_SIZE }>> =
core::mem::MaybeUninit::uninit();

/// Platform store.
#[used]
#[no_mangle]
#[link_section = ".pstore"]
pub static mut PSTORE: [u8; openemc_shared::PSTORE_SIZE] = [0; openemc_shared::PSTORE_SIZE];

/// Signature value for backup register.
pub const BACKUP_REG_SIGNATURE_VALUE: u16 = 0xb001;

Expand Down
2 changes: 1 addition & 1 deletion openemc-driver/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
obj-m += openemc.o openemc_adc.o openemc_battery.o openemc_gpio.o \
openemc_pinctrl.o openemc_power.o openemc_pwm.o openemc_rtc.o \
openemc_supply.o openemc_wdt.o
openemc_supply.o openemc_wdt.o openemc_pstore.o

all:
make -C ${LINUX_DIR} M=$(PWD) modules
Expand Down
6 changes: 6 additions & 0 deletions openemc-driver/openemc.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@
#define OPENEMC_SUPPLY_CURRENT 0x95
#define OPENEMC_SUPPLY_CONNECT_DATA 0x96

/* Platform store register definitions */
#define OPENEMC_PSTORE_SIZE 0xa0
#define OPENEMC_PSTORE_IO_ADDRESS 0xa1
#define OPENEMC_PSTORE_IO_SIZE 0xa2
#define OPENEMC_PSTORE_IO 0xa3

/* Board IO register definitions */
#define OPENEMC_BOARD_IO 0xe0
#define OPENEMC_BOARD_IO_STATUS 0xe1
Expand Down
182 changes: 182 additions & 0 deletions openemc-driver/openemc_pstore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Platform store driver for OpenEMC
*
* Copyright (C) 2022-2024 Sebastian Urban <surban@surban.net>
*/

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/pstore_blk.h>
#include <linux/delay.h>

#include "openemc.h"

static const struct of_device_id openemc_pstore_of_match[] = {
{ .compatible = "openemc,openemc_pstore" },
{}
};
MODULE_DEVICE_TABLE(of, openemc_pstore_of_match);

static struct openemc_pstore_context {
struct openemc *emc;
struct pstore_blk_config info;
struct pstore_device_info dev;
} oops_cxt;

static ssize_t openemc_pstore_read(char *buf, size_t size, loff_t off)
{
struct openemc *emc = oops_cxt.emc;
u16 io_size = 0;
size_t pos = 0;
int ret;

ret = openemc_write_u16(emc, OPENEMC_PSTORE_IO_ADDRESS, off);
if (ret < 0)
return ret;

while (pos < size) {
u16 new_io_size =
min_t(size_t, OPENEMC_MAX_DATA_SIZE, size - pos);
if (io_size != new_io_size) {
ret = openemc_write_u16(emc, OPENEMC_PSTORE_IO_SIZE,
new_io_size);
if (ret < 0)
return ret;

io_size = new_io_size;
}

ret = openemc_read_data(emc, OPENEMC_PSTORE_IO, io_size,
buf + pos);
if (ret < 0)
return ret;

pos += io_size;
}

return size;
}

static ssize_t openemc_pstore_write(const char *buf, size_t size, loff_t off)
{
struct openemc *emc = oops_cxt.emc;
u16 io_size = 0;
size_t pos = 0;
int ret;

ret = openemc_write_u16(emc, OPENEMC_PSTORE_IO_ADDRESS, off);
if (ret < 0)
return ret;

while (pos < size) {
u16 new_io_size =
min_t(size_t, OPENEMC_MAX_DATA_SIZE, size - pos);
if (io_size != new_io_size) {
ret = openemc_write_u16(emc, OPENEMC_PSTORE_IO_SIZE,
new_io_size);
if (ret < 0)
return ret;

io_size = new_io_size;
}

ret = openemc_write_data(emc, OPENEMC_PSTORE_IO, io_size,
buf + pos);
if (ret < 0)
return ret;

pos += io_size;
}

return size;
}

static ssize_t openemc_pstore_panic_write(const char *buf, size_t size,
loff_t off)
{
struct openemc *emc = oops_cxt.emc;

if (mutex_is_locked(&emc->req_lock)) {
pr_emerg("Breaking OpenEMC lock for panic write\n");
mutex_unlock(&emc->req_lock);
}

return openemc_pstore_write(buf, size, off);
}

static int openemc_pstore_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct openemc_pstore_context *cxt = &oops_cxt;
u16 total_size;
int ret;

match = of_match_node(openemc_pstore_of_match, pdev->dev.of_node);
if (!match)
return -ENXIO;

cxt->emc = dev_get_drvdata(pdev->dev.parent);

ret = pstore_blk_get_config(&cxt->info);
if (ret)
return ret;

if (!cxt->info.kmsg_size) {
pr_err("no backend enabled (kmsg_size is 0)\n");
return -EINVAL;
}

ret = openemc_read_u16(cxt->emc, OPENEMC_PSTORE_SIZE, &total_size);
if (ret < 0)
return ret;

if (total_size < 4096) {
dev_err(&pdev->dev, "OpenEMC pstore buffer is too small");
return -EINVAL;
}

cxt->dev.flags = PSTORE_FLAGS_DMESG;
cxt->dev.zone.read = openemc_pstore_read;
cxt->dev.zone.write = openemc_pstore_write;
cxt->dev.zone.erase = NULL;
cxt->dev.zone.panic_write = openemc_pstore_panic_write;
cxt->dev.zone.total_size = total_size;

ret = register_pstore_device(&cxt->dev);
if (ret) {
dev_err(&pdev->dev, "cannot register pstore device: %d\n", ret);
return ret;
}

dev_info(&pdev->dev, "OpenEMC pstore registered with %d bytes\n",
total_size);

return 0;
}

static int openemc_pstore_remove(struct platform_device *pdev)
{
struct openemc_pstore_context *cxt = &oops_cxt;

unregister_pstore_device(&cxt->dev);
cxt->emc = NULL;

return 0;
}

static struct platform_driver openemc_pstore_driver = {
.driver = {
.name = "openemc_pstore",
.of_match_table = of_match_ptr(openemc_pstore_of_match),
},
.probe = openemc_pstore_probe,
.remove = openemc_pstore_remove,
};
module_platform_driver(openemc_pstore_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sebastian Urban <surban@surban.net>");
MODULE_DESCRIPTION("OpenEMC Platform Store");
MODULE_VERSION("0.1");
7 changes: 7 additions & 0 deletions openemc-firmware/memory.x
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ SECTIONS
KEEP(*(.defmt_log .defmt_log.*));
. = ALIGN(8);
} > RAM

/* Pstore buffer. */
.pstore (NOLOAD) : ALIGN(8)
{
KEEP(*(.pstore .pstore.*));
. = ALIGN(8);
} > RAM
}
INSERT BEFORE .data;

Expand Down
5 changes: 5 additions & 0 deletions openemc-firmware/src/i2c_reg_slave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@ impl<const BUFFER: usize> Deref for Response<BUFFER> {
}

impl<const BUFFER: usize> Response<BUFFER> {
/// Buffer size.
pub const fn buffer() -> usize {
BUFFER
}

/// Provides the register value.
///
/// The length of the value must not exceed `BUFFER`.
Expand Down
48 changes: 47 additions & 1 deletion openemc-firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ mod i2c_reg_slave;
mod i2c_slave;
mod irq;
mod pio;
mod pstore;
mod pwm;
mod reg;
mod rtc;
Expand Down Expand Up @@ -123,7 +124,7 @@ static MFD_CELL_PREFIX: &[u8] = b"openemc,openemc_";

/// MFD cells.
static MFD_CELLS: &[&[u8]] =
&[b"adc", b"battery", b"gpio", b"pinctrl", b"power", b"pwm", b"rtc", b"supply", b"wdt"];
&[b"adc", b"battery", b"gpio", b"pinctrl", b"power", b"pwm", b"rtc", b"supply", b"wdt", b"pstore"];

/// Supported EMC models.
static EMC_MODELS: &[u8] = &[0x01, 0xd1];
Expand Down Expand Up @@ -160,6 +161,12 @@ static mut BOOTLOADER_LOG_REF: Option<
&'static mut defmt_ringbuf::RingBuffer<{ openemc_shared::BOOTLOADER_LOG_SIZE }>,
> = None;

/// Platform store.
#[used]
#[no_mangle]
#[link_section = ".pstore"]
pub static mut PSTORE: [u8; openemc_shared::PSTORE_SIZE] = [0; openemc_shared::PSTORE_SIZE];

extern "C" {
/// Space reserved for bootloader (from linker).
pub static __bootloader_max_size: c_void;
Expand Down Expand Up @@ -327,6 +334,8 @@ mod app {
bootloader_crc32: u32,
/// Power on due to charger attachment.
charger_attachment_power_on: bool,
/// Persistent platform store.
pstore: pstore::Pstore<'static>,
}

/// Exclusive resources.
Expand Down Expand Up @@ -682,6 +691,14 @@ mod app {
ThisBoard::PWM_TIMERS[3].then(|| PwmTimer::new(pwm::Timer::Timer4, &clocks)),
];

// Initialize platform store.
let pstore = unsafe {
use core::ptr::addr_of_mut;
// SAFETY: PSTORE is only accessed here and nowhere else.
pstore::Pstore::new(&mut *addr_of_mut!(PSTORE))
};
defmt::info!("Platform store of size {} bytes", pstore.data.len());

// Configure IRQ pin.
let mut usable_exti = 0b0000_0000_0000_1111_1111_1111_1111_1111;
board.limit_usable_exti(&mut usable_exti);
Expand Down Expand Up @@ -755,6 +772,7 @@ mod app {
undervoltage_power_off: false,
bootloader_crc32,
charger_attachment_power_on,
pstore,
},
Local { i2c_reg_slave, adc, adc_inp, ugpio, pwm_timers },
init::Monotonics(mono),
Expand Down Expand Up @@ -1495,6 +1513,7 @@ mod app {
cfg,
&bootloader_crc32,
&charger_attachment_power_on,
pstore,
],
priority = 2,
)]
Expand Down Expand Up @@ -1906,6 +1925,33 @@ mod app {
Event::Write { reg: reg::SUPPLY_CONNECT_DATA, value } => {
cx.shared.power_supply_connect_data.lock(|connect| *connect = value.as_u8() != 0);
}
Event::Read { reg: reg::PSTORE_SIZE } => {
cx.shared.pstore.lock(|pstore| respond_u16(pstore.data.len() as _));
}
Event::Read { reg: reg::PSTORE_IO_ADDRESS } => {
cx.shared.pstore.lock(|pstore| respond_u16(pstore.io_address as _));
}
Event::Write { reg: reg::PSTORE_IO_ADDRESS, value } => {
cx.shared.pstore.lock(|pstore| pstore.io_address = value.as_u16() as _);
}
Event::Read { reg: reg::PSTORE_IO_SIZE } => {
cx.shared.pstore.lock(|pstore| respond_u16(pstore.io_size as _));
}
Event::Write { reg: reg::PSTORE_IO_SIZE, value } => {
cx.shared.pstore.lock(|pstore| pstore.io_size = value.as_u16() as _);
}
Event::Read { reg: reg::PSTORE_IO } => {
cx.shared.pstore.lock(|pstore| {
let mut buf = [0; I2C_BUFFER_SIZE];
pstore.read(&mut buf);
respond_slice(&buf);
});
}
Event::Write { reg: reg::PSTORE_IO, value } => {
cx.shared.pstore.lock(|pstore| {
pstore.write(&value);
});
}
Event::Read { reg: reg::BOARD_IO } => {
cx.shared.board.lock(|board| {
let data = board.io_read();
Expand Down
Loading

0 comments on commit f46bd16

Please sign in to comment.