Skip to content

Commit

Permalink
Merge pull request #104 from GaloisInc/89-gpio-linux
Browse files Browse the repository at this point in the history
Add Linux GPIO support to MPS

This updates MPS with the ability to send actuator outputs to a Linux GPIO device.  For example, building with `make GPIO=Enabled` and running with `MPS_GPIO_DEVICE=/dev/gpiochip1 ./mps` will cause MPS to output the current actuator state on GPIO lines 0 and 1 of `/dev/gpiochip1`.  (It also displays the actuator state in the human-readable UART output as usual.)

This PR also updates the MPS test scripts to check the GPIO behavior in addition to the UART output.  The CI is updated to run the VM tests in this mode, so the new GPIO support is tested in CI.  However, this only applies to MPS test cases that explicitly check the actuator state, which at the moment is only `scenarios/normal_5a` (`normal_6` also checks the actuator state, but only the first few cases are run when `QUICK=1`, and these don't trigger any actuators).
  • Loading branch information
spernsteiner authored Aug 5, 2024
2 parents a122d23 + 22001f1 commit 5e01fcb
Show file tree
Hide file tree
Showing 23 changed files with 674 additions and 80 deletions.
111 changes: 91 additions & 20 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,53 @@ on:
jobs:
mps-build:
runs-on: ubuntu-22.04
needs: libgpiod
steps:
- name: Checkout repository and submodules
- name: Checkout repository
uses: actions/checkout@v4
- name: Install aarch64 toolchain
- name: Checkout submodules
run: |
git submodule update --init src/pkvm_setup/libgpiod
- name: Hash inputs
id: hash
run: |
cache_key="$(bash src/pkvm_setup/package.sh cache_key mps)"
echo "Cache key: $cache_key"
echo "CACHE_KEY=$cache_key" >>$GITHUB_OUTPUT
echo "CACHE_KEY=$cache_key" >>$GITHUB_ENV
- name: Cache results
id: cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: packages/${{ env.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit == 'true' }}
name: "Unpack mps"
run: |
tar -xvf packages/${{ env.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: "Cache restore: libgpiod"
uses: actions/cache/restore@v3
with:
key: ${{ needs.libgpiod.outputs.CACHE_KEY }}
path: packages/${{ needs.libgpiod.outputs.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Install aarch64 toolchain
run: sudo apt-get install -y {gcc,g++}-aarch64-linux-gnu
- name: Install verilator
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Install verilator
run: sudo apt-get install -y verilator
- name: Build MPS
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Build MPS
run: |
cd components/mission_protection_system/src
make clean
# Build, then check it created the correct output file.
make mps_bottom CONFIG=self_test
[ -f mps_bottom.self_test ]
make mps CONFIG=self_test
[ -f mps.self_test ]
make mps CONFIG=no_self_test
[ -f mps.no_self_test ]
make mps CONFIG=self_test TARGET=aarch64
[ -f mps.self_test.aarch64 ]
make mps CONFIG=no_self_test TARGET=aarch64
[ -f mps.no_self_test.aarch64 ]
bash src/pkvm_setup/package.sh full_build mps
- name: Upload MPS binaries
uses: actions/upload-artifact@v4
with:
name: mps-binaries
path: components/mission_protection_system/src/mps.*
name: mps-binaries
path: components/mission_protection_system/src/mps.*
outputs:
CACHE_KEY: ${{ steps.hash.outputs.CACHE_KEY }}

mps-test:
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -113,8 +133,42 @@ jobs:
outputs:
CACHE_KEY: ${{ steps.hash.outputs.CACHE_KEY }}

libgpiod:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Checkout submodules
run: |
git submodule update --init src/pkvm_setup/libgpiod
- name: Hash inputs
id: hash
run: |
cache_key="$(bash src/pkvm_setup/package.sh cache_key libgpiod)"
echo "Cache key: $cache_key"
echo "CACHE_KEY=$cache_key" >>$GITHUB_OUTPUT
echo "CACHE_KEY=$cache_key" >>$GITHUB_ENV
- name: Cache results
id: cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: packages/${{ env.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Install dependency packages
run: |
sudo apt-get install -y \
build-essential autoconf automake autoconf-archive \
gcc-aarch64-linux-gnu
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Build libgpiod
run: |
bash src/pkvm_setup/package.sh full_build libgpiod
outputs:
CACHE_KEY: ${{ steps.hash.outputs.CACHE_KEY }}

vhost_device:
runs-on: ubuntu-22.04
needs: libgpiod
steps:
- uses: actions/checkout@v4
- name: Checkout submodules
Expand All @@ -135,6 +189,12 @@ jobs:
with:
key: ${{ env.CACHE_KEY }}
path: packages/${{ env.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: "Cache restore: libgpiod"
uses: actions/cache/restore@v3
with:
key: ${{ needs.libgpiod.outputs.CACHE_KEY }}
path: packages/${{ needs.libgpiod.outputs.CACHE_KEY }}.tar.gz
- if: ${{ steps.cache.outputs.cache-hit != 'true' }}
name: Install dependency packages
run: |
Expand Down Expand Up @@ -309,10 +369,13 @@ jobs:
CACHE_KEY: ${{ steps.hash.outputs.CACHE_KEY }}

mps-test-vm:
runs-on: ubuntu-22.04
# Other jobs use Ubuntu 22.04, but that release has an old version of QEMU
# (6.2) and doesn't support vhost-user-gpio devices.
runs-on: ubuntu-24.04
needs:
- mps-build
- vm_images
- vhost_device
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -329,9 +392,17 @@ jobs:
with:
key: ${{ needs.vm_images.outputs.CACHE_KEY }}
path: packages/${{ needs.vm_images.outputs.CACHE_KEY }}.tar.gz
- name: "Cache restore: vhost_device"
uses: actions/cache/restore@v3
with:
key: ${{ needs.vhost_device.outputs.CACHE_KEY }}
path: packages/${{ needs.vhost_device.outputs.CACHE_KEY }}.tar.gz
- name: "Unpack vm_images"
run: |
tar -xvf packages/${{ needs.vm_images.outputs.CACHE_KEY }}.tar.gz
- name: "Unpack vhost_device"
run: |
tar -xvf packages/${{ needs.vhost_device.outputs.CACHE_KEY }}.tar.gz
- name: Install QEMU
run: sudo apt-get install -y qemu-system-arm
- uses: hecrj/setup-rust-action@v2
Expand Down
21 changes: 19 additions & 2 deletions components/mission_protection_system/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ifeq ($(TARGET),x86_64)
CC = x86_64-linux-gnu-gcc
CXX = x86_64-linux-gnu-g++
GPIO ?= Disabled
LIBGPIOD_BUILD_DIR = build
else ifeq ($(TARGET),aarch64)
CC = aarch64-linux-gnu-gcc
CXX = aarch64-linux-gnu-g++
GPIO ?= Enabled
LIBGPIOD_BUILD_DIR = build.aarch64
else ifeq ($(TARGET),)
# If target is unspecified, use clang and its default target.
CC = clang
CXX = clang++
GPIO ?= Disabled
LIBGPIOD_BUILD_DIR = build
else
$(error "bad TARGET $(TARGET)")
endif
Expand Down Expand Up @@ -142,6 +148,7 @@ CFLAGS += -DDEBUG
endif



OBJS_C = \
$(BUILD_DIR)/core.c.o \
$(BUILD_DIR)/common.c.o \
Expand Down Expand Up @@ -169,7 +176,17 @@ OBJS_SV = \
OBJS = $(OBJS_C) $(OBJS_VERILATOR) $(OBJS_SV)


BUILD_MSG = BUILD CC=$(CC) PLATFORM=$(PLATFORM) EXECUTION=$(EXECUTION) SELF_TEST_PERIOD_SEC=$(SELF_TEST_PERIOD_SEC) SELF_TEST=$(SELF_TEST)
ifeq ($(GPIO),Enabled)
CFLAGS += -DENABLE_GPIO
CFLAGS += -I ../../../src/pkvm_setup/libgpiod/include/
LIBS += \
-L ../../../src/pkvm_setup/libgpiod/$(LIBGPIOD_BUILD_DIR)/lib/.libs \
-lgpiod
OBJS_C += $(BUILD_DIR)/gpio_linux.c.o
endif


BUILD_MSG = BUILD CC=$(CC) PLATFORM=$(PLATFORM) EXECUTION=$(EXECUTION) SELF_TEST_PERIOD_SEC=$(SELF_TEST_PERIOD_SEC) SELF_TEST=$(SELF_TEST) GPIO=$(GPIO)

.PHONY: all clean generate_sources generate_c generate_sv

Expand All @@ -186,7 +203,7 @@ endif
all: $(MPS_BIN)

$(MPS_BIN): $(OBJS)
$(CXX) $(CFLAGS) $(LIBS) -o $@ $^
$(CXX) $(CFLAGS) -o $@ $^ $(LIBS)
@echo "***"
@echo $(BUILD_MSG)
@echo "***"
Expand Down
14 changes: 13 additions & 1 deletion components/mission_protection_system/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Besides a normal `clang` toolchain, the `Makefile` targets depend on the followi
11.6.1) it seems there is an issue with recent versions: version 4.108 seems
to work.
- `Cryptol` 2.11 <https://cryptol.net>

- `libgpiod` version 2 (optional; see GPIO section below)

Verification with `frama-c` additionally requires `frama-c` version 24.0
<https://frama-c.com>.

Expand All @@ -38,6 +39,8 @@ implemented) on a NERV-based SoC.
and 10^-5 lb/in^2, respectively)
- `SELF_TEST=Enabled` (default) builds the `mps` with a periodic self-test
feature. `SELF_TEST=Disabled` builds the system without this feature.
- `GPIO=Enabled` builds the `mps` with GPIO support, as described below. This
requires `libgpiod`.

## UI

Expand Down Expand Up @@ -193,6 +196,15 @@ states inbetween individual writes are consistent. Therefore, it is
only necessary to guarantee that individual writes (to shared
locations) are made atomically.

## GPIO

When built with `GPIO=Enabled`, the MPS can output the current actuator state
to a Linux GPIO device (e.g. `/dev/gpiochip1`). This is enabled by running
`./mps` with the environment variable `MPS_GPIO_DEVICE` set to the device path.
The MPS will configure lines 0 and 1 of the target GPIO device as outputs and
set each line to be active when the corresponding actuator is `ON` and inactive
when it is `OFF`.

## License

Copyright 2021, 2022, 2023 Galois, Inc.
Expand Down
7 changes: 7 additions & 0 deletions components/mission_protection_system/src/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#include "printf.h"
#endif

#ifdef ENABLE_GPIO
#include "gpio_linux.h"
#endif

struct core_state core = {0};
struct instrumentation_state instrumentation[4];
struct actuation_logic actuation_logic[2];
Expand Down Expand Up @@ -172,6 +176,9 @@ int set_actuate_device(uint8_t device_no, uint8_t on)
MUTEX_LOCK(&mem_mutex);
actuator_state[device_no] = on;
MUTEX_UNLOCK(&mem_mutex);
#ifdef ENABLE_GPIO
gpio_set_value(device_no, on);
#endif
DEBUG_PRINTF(("<common.c> set_actuate_device: dev %u, on %u\n",device_no, on));
return 0;
}
Expand Down
114 changes: 114 additions & 0 deletions components/mission_protection_system/src/gpio_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// GPIO implementation for Linux, using libgpiod

#include <stdio.h>
#include <stdlib.h>
#include <gpiod.h>
#include "common.h"
// Include `platform.h` for `ASSERT`
#include "platform.h"

#define NUM_LINES NDEV

// BEGIN code copied from libgpiod `examples/toggle_multiple_line_values.c`

static struct gpiod_line_request *
request_output_lines(const char *chip_path, const unsigned int *offsets,
enum gpiod_line_value *values, unsigned int num_lines,
const char *consumer)
{
struct gpiod_request_config *rconfig = NULL;
struct gpiod_line_request *request = NULL;
struct gpiod_line_settings *settings;
struct gpiod_line_config *lconfig;
struct gpiod_chip *chip;
unsigned int i;
int ret;

chip = gpiod_chip_open(chip_path);
if (!chip)
return NULL;

settings = gpiod_line_settings_new();
if (!settings)
goto close_chip;

gpiod_line_settings_set_direction(settings,
GPIOD_LINE_DIRECTION_OUTPUT);

lconfig = gpiod_line_config_new();
if (!lconfig)
goto free_settings;

for (i = 0; i < num_lines; i++) {
ret = gpiod_line_config_add_line_settings(lconfig, &offsets[i],
1, settings);
if (ret)
goto free_line_config;
}
gpiod_line_config_set_output_values(lconfig, values, num_lines);

if (consumer) {
rconfig = gpiod_request_config_new();
if (!rconfig)
goto free_line_config;

gpiod_request_config_set_consumer(rconfig, consumer);
}

request = gpiod_chip_request_lines(chip, rconfig, lconfig);

gpiod_request_config_free(rconfig);

free_line_config:
gpiod_line_config_free(lconfig);

free_settings:
gpiod_line_settings_free(settings);

close_chip:
gpiod_chip_close(chip);

return request;
}

// END code copied from libgpiod `examples/toggle_multiple_line_values.c`

static int request_inited = 0;
static struct gpiod_line_request* request = NULL;

// Hardcoded device and lines for use in OpenSUT VMs.
static const unsigned int line_offsets[NUM_LINES] = {0, 1};

static void init_request() {
if (!request_inited) {
request_inited = 1;
const char* chip_path = getenv("MPS_GPIO_DEVICE");
if (chip_path == NULL) {
// No device is configured. Leave `request` as `NULL`, so no GPIO output
// will be performed.
return;
}

enum gpiod_line_value values[NUM_LINES] = { GPIOD_LINE_VALUE_INACTIVE,
GPIOD_LINE_VALUE_INACTIVE };
request = request_output_lines(chip_path, line_offsets, values, NUM_LINES, "mps");
if (request == NULL) {
fprintf(stderr, "warning: failed to open GPIO device %s\n", chip_path);
}
}
}

void gpio_set_value(int index, int value) {
enum gpiod_line_value gpiod_value;
if (value == 0) {
gpiod_value = GPIOD_LINE_VALUE_INACTIVE;
} else {
gpiod_value = GPIOD_LINE_VALUE_ACTIVE;
}
unsigned int gpiod_offset = line_offsets[index];

init_request();
if (request != NULL) {
gpiod_line_request_set_value(request, gpiod_offset, gpiod_value);
}
}
8 changes: 8 additions & 0 deletions components/mission_protection_system/src/gpio_linux.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef GPIO_LINUX_H_
#define GPIO_LINUX_H_

// Set the value of GPIO output `index` to `value`. This will open the GPIO
// device the first time it's called.
void gpio_set_value(int index, int value);

#endif // GPIO_LINUX_H_
Loading

0 comments on commit 5e01fcb

Please sign in to comment.