Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
gpio-mockup-cdev
gpio-chip-info
gpio-line-name
gpio-cdev-uaf
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh gpio-cdev-uaf.sh
TEST_FILES := gpio-mockup-sysfs.sh
TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name \
gpio-cdev-uaf
CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES)
include ../lib.mk
@@ -0,0 +1,5 @@
CONFIG_GPIOLIB=y
CONFIG_GPIO_CDEV=y
CONFIG_GPIO_MOCKUP=m
CONFIG_GPIO_SIM=m
CONFIG_GPIO_AGGREGATOR=m
+727
View File
@@ -0,0 +1,727 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2025 Bartosz Golaszewski <brgl@bgdev.pl>
# Copyright (C) 2025 Koichiro Den <koichiro.den@canonical.com>
BASE_DIR=$(dirname "$0")
CONFIGFS_SIM_DIR="/sys/kernel/config/gpio-sim"
CONFIGFS_AGG_DIR="/sys/kernel/config/gpio-aggregator"
SYSFS_AGG_DIR="/sys/bus/platform/drivers/gpio-aggregator"
MODULE="gpio-aggregator"
fail() {
echo "$*" >&2
echo "GPIO $MODULE test FAIL"
exit 1
}
skip() {
echo "$*" >&2
echo "GPIO $MODULE test SKIP"
exit 4
}
# gpio-sim
sim_enable_chip() {
local CHIP=$1
echo 1 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to enable the chip"
}
sim_disable_chip() {
local CHIP=$1
echo 0 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to disable the chip"
}
sim_configfs_cleanup() {
local NOCHECK=${1:-0}
for CHIP_DIR in "$CONFIGFS_SIM_DIR"/*; do
[ -d "$CHIP_DIR" ] || continue
echo 0 > "$CHIP_DIR/live"
find "$CHIP_DIR" -depth -type d -exec rmdir {} \;
done
[ "$NOCHECK" -eq 1 ] && return;
remaining=$(find "$CONFIGFS_SIM_DIR" -mindepth 1 -type d 2> /dev/null)
if [ -n "$remaining" ]; then
fail "Directories remain in $CONFIGFS_SIM_DIR: $remaining"
fi
}
sim_get_chip_label() {
local CHIP=$1
local BANK=$2
local CHIP_NAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name" 2> /dev/null) || \
fail "Unable to read the chip name from configfs"
$BASE_DIR/gpio-chip-info "/dev/$CHIP_NAME" label || \
fail "Unable to read the chip label from the character device"
}
# gpio-aggregator
agg_create_chip() {
local CHIP=$1
mkdir "$CONFIGFS_AGG_DIR/$CHIP"
}
agg_remove_chip() {
local CHIP=$1
find "$CONFIGFS_AGG_DIR/$CHIP/" -depth -type d -exec rmdir {} \; || \
fail "Unable to remove $CONFIGFS_AGG_DIR/$CHIP"
}
agg_create_line() {
local CHIP=$1
local LINE=$2
mkdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE"
}
agg_remove_line() {
local CHIP=$1
local LINE=$2
rmdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE"
}
agg_set_key() {
local CHIP=$1
local LINE=$2
local KEY=$3
echo "$KEY" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/key" || fail "Unable to set the lookup key"
}
agg_set_offset() {
local CHIP=$1
local LINE=$2
local OFFSET=$3
echo "$OFFSET" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/offset" || \
fail "Unable to set the lookup offset"
}
agg_set_line_name() {
local CHIP=$1
local LINE=$2
local NAME=$3
echo "$NAME" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/name" || fail "Unable to set the line name"
}
agg_enable_chip() {
local CHIP=$1
echo 1 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to enable the chip"
}
agg_disable_chip() {
local CHIP=$1
echo 0 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to disable the chip"
}
agg_configfs_cleanup() {
local NOCHECK=${1:-0}
for CHIP_DIR in "$CONFIGFS_AGG_DIR"/*; do
[ -d "$CHIP_DIR" ] || continue
echo 0 > "$CHIP_DIR/live" 2> /dev/null
find "$CHIP_DIR" -depth -type d -exec rmdir {} \;
done
[ "$NOCHECK" -eq 1 ] && return;
remaining=$(find "$CONFIGFS_AGG_DIR" -mindepth 1 -type d 2> /dev/null)
if [ -n "$remaining" ]; then
fail "Directories remain in $CONFIGFS_AGG_DIR: $remaining"
fi
}
agg_configfs_dev_name() {
local CHIP=$1
cat "$CONFIGFS_AGG_DIR/$CHIP/dev_name" 2> /dev/null || \
fail "Unable to read the device name from configfs"
}
agg_configfs_chip_name() {
local CHIP=$1
local DEV_NAME=$(agg_configfs_dev_name "$CHIP")
local CHIP_LIST=$(find "/sys/devices/platform/$DEV_NAME" \
-maxdepth 1 -type d -name "gpiochip[0-9]*" 2> /dev/null)
local CHIP_COUNT=$(echo "$CHIP_LIST" | wc -l)
if [ -z "$CHIP_LIST" ]; then
fail "No gpiochip in /sys/devices/platform/$DEV_NAME/"
elif [ "$CHIP_COUNT" -ne 1 ]; then
fail "Multiple gpiochips unexpectedly found: $CHIP_LIST"
fi
basename "$CHIP_LIST"
}
agg_get_chip_num_lines() {
local CHIP=$1
local N_DIR=$(ls -d $CONFIGFS_AGG_DIR/$CHIP/line[0-9]* 2> /dev/null | wc -l)
local N_LINES
if [ "$(cat $CONFIGFS_AGG_DIR/$CHIP/live)" = 0 ]; then
echo "$N_DIR"
else
N_LINES=$(
$BASE_DIR/gpio-chip-info \
"/dev/$(agg_configfs_chip_name "$CHIP")" num-lines
) || fail "Unable to read the number of lines from the character device"
if [ $N_DIR != $N_LINES ]; then
fail "Discrepancy between two sources for the number of lines"
fi
echo "$N_LINES"
fi
}
agg_get_chip_label() {
local CHIP=$1
$BASE_DIR/gpio-chip-info "/dev/$(agg_configfs_chip_name "$CHIP")" label || \
fail "Unable to read the chip label from the character device"
}
agg_get_line_name() {
local CHIP=$1
local OFFSET=$2
local NAME_CONFIGFS=$(cat "$CONFIGFS_AGG_DIR/$CHIP/line${OFFSET}/name")
local NAME_CDEV
if [ "$(cat "$CONFIGFS_AGG_DIR/$CHIP/live")" = 0 ]; then
echo "$NAME_CONFIGFS"
else
NAME_CDEV=$(
$BASE_DIR/gpio-line-name \
"/dev/$(agg_configfs_chip_name "$CHIP")" "$OFFSET"
) || fail "Unable to read the line name from the character device"
if [ "$NAME_CONFIGFS" != "$NAME_CDEV" ]; then
fail "Discrepancy between two sources for the name of line"
fi
echo "$NAME_CDEV"
fi
}
# Load the modules. This will pull in configfs if needed too.
modprobe gpio-sim || skip "unable to load the gpio-sim module"
modprobe gpio-aggregator || skip "unable to load the gpio-aggregator module"
# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed.
for IDX in $(seq 5); do
if [ "$IDX" -eq "5" ]; then
skip "configfs not mounted at /sys/kernel/config"
fi
mountpoint -q /sys/kernel/config && break
sleep 0.1
done
# If the module was already loaded: remove all previous chips
agg_configfs_cleanup
sim_configfs_cleanup
trap "exit 1" SIGTERM SIGINT
trap "agg_configfs_cleanup 1; sim_configfs_cleanup 1" EXIT
# Use gpio-sim chips as the test backend
for CHIP in $(seq -f "chip%g" 0 1); do
mkdir $CONFIGFS_SIM_DIR/$CHIP
for BANK in $(seq -f "bank%g" 0 1); do
mkdir -p "$CONFIGFS_SIM_DIR/$CHIP/$BANK"
echo "${CHIP}_${BANK}" > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/label" || \
fail "unable to set the chip label"
echo 16 > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/num_lines" || \
fail "unable to set the number of lines"
for IDX in $(seq 0 15); do
LINE_NAME="${CHIP}${BANK}_${IDX}"
LINE_DIR="$CONFIGFS_SIM_DIR/$CHIP/$BANK/line$IDX"
mkdir -p $LINE_DIR
echo "$LINE_NAME" > "$LINE_DIR/name" || fail "unable to set the line name"
done
done
sim_enable_chip "$CHIP"
done
echo "1. GPIO aggregator creation/deletion"
echo "1.1. Creation/deletion via configfs"
echo "1.1.1. Minimum creation/deletion"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_offset agg0 line0 5
agg_set_line_name agg0 line0 test0
agg_enable_chip agg0
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines agg0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset"
agg_disable_chip agg0
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.2. Complex creation/deletion"
agg_create_chip agg0
agg_create_line agg0 line0
agg_create_line agg0 line1
agg_create_line agg0 line2
agg_create_line agg0 line3
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_key agg0 line2 "$(sim_get_chip_label chip1 bank0)"
agg_set_key agg0 line3 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset agg0 line0 1
agg_set_offset agg0 line1 3
agg_set_offset agg0 line2 5
agg_set_offset agg0 line3 7
agg_set_line_name agg0 line0 test0
agg_set_line_name agg0 line1 test1
agg_set_line_name agg0 line2 test2
agg_set_line_name agg0 line3 test3
agg_enable_chip agg0
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines agg0)" = "4" || fail "number of lines is not 1"
test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset"
test "$(agg_get_line_name agg0 1)" = "test1" || fail "line name is unset"
test "$(agg_get_line_name agg0 2)" = "test2" || fail "line name is unset"
test "$(agg_get_line_name agg0 3)" = "test3" || fail "line name is unset"
agg_disable_chip agg0
agg_remove_line agg0 line0
agg_remove_line agg0 line1
agg_remove_line agg0 line2
agg_remove_line agg0 line3
agg_remove_chip agg0
echo "1.1.3. Can't instantiate a chip without any line"
agg_create_chip agg0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
agg_remove_chip agg0
echo "1.1.4. Can't instantiate a chip with invalid configuration"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "chipX_bankX"
agg_set_offset agg0 line0 99
agg_set_line_name agg0 line0 test0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.5. Can't instantiate a chip asynchronously via deferred probe"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "chip0_bank0"
agg_set_offset agg0 line0 5
agg_set_line_name agg0 line0 test0
sim_disable_chip chip0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
sim_enable_chip chip0
sleep 1
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || \
fail "chip unexpectedly transitioned to 'live' state"
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.6. Can't instantiate a chip with _sysfs prefix"
mkdir "$CONFIGFS_AGG_DIR/_sysfs" 2> /dev/null && fail "chip _sysfs unexpectedly created"
mkdir "$CONFIGFS_AGG_DIR/_sysfs.foo" 2> /dev/null && fail "chip _sysfs.foo unexpectedly created"
echo "1.2. Creation/deletion via sysfs"
echo "1.2.1. Minimum creation/deletion"
echo "chip0_bank0 0" > "$SYSFS_AGG_DIR/new_device"
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.2. Complex creation/deletion"
echo "chip0bank0_0 chip1_bank1 10-11" > "$SYSFS_AGG_DIR/new_device"
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "3" || fail "number of lines is not 3"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 1)" = "" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 2)" = "" || fail "line name is unset"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.3. Asynchronous creation with deferred probe"
sim_disable_chip chip0
echo 'chip0_bank0 0' > $SYSFS_AGG_DIR/new_device
sleep 1
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive"
sim_enable_chip chip0
sleep 1
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly remains dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name unexpectedly set"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.4. Can't instantiate a chip with invalid configuration"
echo "xyz 0" > "$SYSFS_AGG_DIR/new_device"
test "$(cat $CONFIGFS_AGG_DIR/_sysfs.0/live)" = 0 || fail "chip unexpectedly alive"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
echo "2. GPIO aggregator configuration"
echo "2.1. Configuring aggregators instantiated via configfs"
setup_2_1() {
agg_create_chip agg0
agg_create_line agg0 line0
agg_create_line agg0 line1
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank0)"
agg_set_offset agg0 line0 1
agg_set_offset agg0 line1 3
agg_set_line_name agg0 line0 test0
agg_set_line_name agg0 line1 test1
agg_enable_chip agg0
}
teardown_2_1() {
agg_configfs_cleanup
}
echo "2.1.1. While offline"
echo "2.1.1.1. Line can be added/removed"
setup_2_1
agg_disable_chip agg0
agg_create_line agg0 line2
agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line2 5
agg_enable_chip agg0
test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1"
teardown_2_1
echo "2.1.1.2. Line key can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank1)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank1)"
agg_enable_chip agg0
teardown_2_1
echo "2.1.1.3. Line name can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_line_name agg0 line0 new0
agg_set_line_name agg0 line1 new1
agg_enable_chip agg0
test "$(agg_get_line_name agg0 0)" = "new0" || fail "line name is unset"
test "$(agg_get_line_name agg0 1)" = "new1" || fail "line name is unset"
teardown_2_1
echo "2.1.1.4. Line offset can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_offset agg0 line0 5
agg_set_offset agg0 line1 7
agg_enable_chip agg0
teardown_2_1
echo "2.1.1.5. Can re-enable a chip after valid reconfiguration"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset agg0 line0 15
agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line0 14
agg_create_line agg0 line2
agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line2 13
agg_enable_chip agg0
test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1"
teardown_2_1
echo "2.1.1.7. Can't re-enable a chip with invalid reconfiguration"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 invalidkey
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_1
setup_2_1
agg_disable_chip agg0
agg_set_offset agg0 line0 99
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_1
echo "2.1.2. While online"
echo "2.1.2.1. Can't add/remove line"
setup_2_1
mkdir "$CONFIGFS_AGG_DIR/agg0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/agg0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_1
echo "2.1.2.2. Can't modify line key"
setup_2_1
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/agg0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_1
echo "2.1.2.3. Can't modify line name"
setup_2_1
echo "new0" > "$CONFIGFS_AGG_DIR/agg0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_1
echo "2.1.2.4. Can't modify line offset"
setup_2_1
echo "5" > "$CONFIGFS_AGG_DIR/agg0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_1
echo "2.2. Configuring aggregators instantiated via sysfs"
setup_2_2() {
echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device"
}
teardown_2_2() {
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
}
echo "2.2.1. While online"
echo "2.2.1.1. Can toggle live"
setup_2_2
agg_disable_chip _sysfs.0
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.1.2. Can't add/remove line"
setup_2_2
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
echo "2.2.1.3. Can't modify line key"
setup_2_2
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_2
echo "2.2.1.4. Can't modify line name"
setup_2_2
echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_2
echo "2.2.1.5. Can't modify line offset"
setup_2_2
echo "5" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_2
echo "2.2.2. While waiting for deferred probe"
echo "2.2.2.1. Can't add/remove line despite live = 0"
sim_disable_chip chip0
setup_2_2
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.2. Can't modify line key"
sim_disable_chip chip0
setup_2_2
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.3. Can't modify line name"
sim_disable_chip chip0
setup_2_2
echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.4. Can't modify line offset"
sim_disable_chip chip0
setup_2_2
echo 5 > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.5. Can't toggle live"
sim_disable_chip chip0
setup_2_2
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive"
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
sim_enable_chip chip0
echo "2.2.3. While offline"
echo "2.2.3.1. Can't add/remove line despite live = 0"
setup_2_2
agg_disable_chip _sysfs.0
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
echo "2.2.3.2. Line key can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip0 bank1)"
agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip1 bank1)"
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.3. Line name can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_line_name _sysfs.0 line0 new0
agg_set_line_name _sysfs.0 line1 new1
agg_enable_chip _sysfs.0
test "$(agg_get_line_name _sysfs.0 0)" = "new0" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 1)" = "new1" || fail "line name is unset"
teardown_2_2
echo "2.2.3.4. Line offset can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_offset _sysfs.0 line0 5
agg_set_offset _sysfs.0 line1 7
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.5. Can re-enable a chip with valid reconfiguration"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset _sysfs.0 line0 15
agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset _sysfs.0 line0 14
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.6. Can't re-enable a chip with invalid reconfiguration"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 invalidkey
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
setup_2_2
agg_disable_chip _sysfs.0
agg_set_offset _sysfs.0 line0 99
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
echo "3. Module unload"
echo "3.1. Can't unload module if there is at least one device created via configfs"
agg_create_chip agg0
modprobe -r gpio-aggregator 2> /dev/null
test -d /sys/module/gpio_aggregator || fail "module unexpectedly unloaded"
agg_remove_chip agg0
echo "3.2. Can unload module if there is no device created via configfs"
echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device"
modprobe -r gpio-aggregator 2> /dev/null
test -d /sys/module/gpio_aggregator && fail "module unexpectedly remains to be loaded"
modprobe gpio-aggregator 2> /dev/null
echo "4. GPIO forwarder functional"
SETTINGS="chip0:bank0:2 chip0:bank1:4 chip1:bank0:6 chip1:bank1:8"
setup_4() {
local OFFSET=0
agg_create_chip agg0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
agg_create_line agg0 "line${OFFSET}"
agg_set_key agg0 "line${OFFSET}" "$(sim_get_chip_label "$CHIP" "$BANK")"
agg_set_offset agg0 "line${OFFSET}" "$LINE"
OFFSET=$(expr $OFFSET + 1)
done
agg_enable_chip agg0
}
teardown_4() {
agg_configfs_cleanup
}
echo "4.1. Forwarding set values"
setup_4
OFFSET=0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name")
CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name")
VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value"
test $(cat $VAL_PATH) = "0" || fail "incorrect value read from sysfs"
$BASE_DIR/gpio-mockup-cdev -s 1 "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET" &
mock_pid=$!
sleep 0.1 # FIXME Any better way?
test "$(cat $VAL_PATH)" = "1" || fail "incorrect value read from sysfs"
kill "$mock_pid"
OFFSET=$(expr $OFFSET + 1)
done
teardown_4
echo "4.2. Forwarding set config"
setup_4
OFFSET=0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name")
CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name")
VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value"
$BASE_DIR/gpio-mockup-cdev -b pull-up "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET"
test $(cat "$VAL_PATH") = "1" || fail "incorrect value read from sysfs"
OFFSET=$(expr $OFFSET + 1)
done
teardown_4
echo "5. Race condition verification"
echo "5.1. Stress test of new_device/delete_device and module load/unload"
for _ in $(seq 1000); do
{
echo "dummy 0" > "$SYSFS_AGG_DIR/new_device"
cat "$CONFIGFS_AGG_DIR/_sysfs.0/dev_name" > "$SYSFS_AGG_DIR/delete_device"
} 2> /dev/null
done &
writer_pid=$!
while kill -0 "$writer_pid" 2> /dev/null; do
{
modprobe gpio-aggregator
modprobe -r gpio-aggregator
} 2> /dev/null
done
echo "GPIO $MODULE test PASS"
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPIO character device helper for UAF tests.
*
* Copyright 2026 Google LLC
*/
#include <errno.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define CONFIGFS_DIR "/sys/kernel/config/gpio-sim"
#define PROCFS_DIR "/proc"
static void print_usage(void)
{
printf("usage:\n");
printf(" gpio-cdev-uaf [chip|handle|event|req] [poll|read|ioctl]\n");
}
static int _create_chip(const char *name, int create)
{
char path[64];
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s", name);
if (create)
return mkdir(path, 0755);
else
return rmdir(path);
}
static int create_chip(const char *name)
{
return _create_chip(name, 1);
}
static void remove_chip(const char *name)
{
_create_chip(name, 0);
}
static int _create_bank(const char *chip_name, const char *name, int create)
{
char path[64];
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s", chip_name, name);
if (create)
return mkdir(path, 0755);
else
return rmdir(path);
}
static int create_bank(const char *chip_name, const char *name)
{
return _create_bank(chip_name, name, 1);
}
static void remove_bank(const char *chip_name, const char *name)
{
_create_bank(chip_name, name, 0);
}
static int _enable_chip(const char *name, int enable)
{
char path[64];
int fd, ret;
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/live", name);
fd = open(path, O_WRONLY);
if (fd == -1)
return fd;
if (enable)
ret = write(fd, "1", 1);
else
ret = write(fd, "0", 1);
close(fd);
return ret == 1 ? 0 : -1;
}
static int enable_chip(const char *name)
{
return _enable_chip(name, 1);
}
static void disable_chip(const char *name)
{
_enable_chip(name, 0);
}
static int open_chip(const char *chip_name, const char *bank_name)
{
char path[64], dev_name[32];
int ret, fd;
ret = create_chip(chip_name);
if (ret) {
fprintf(stderr, "failed to create chip\n");
return ret;
}
ret = create_bank(chip_name, bank_name);
if (ret) {
fprintf(stderr, "failed to create bank\n");
goto err_remove_chip;
}
ret = enable_chip(chip_name);
if (ret) {
fprintf(stderr, "failed to enable chip\n");
goto err_remove_bank;
}
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s/chip_name",
chip_name, bank_name);
fd = open(path, O_RDONLY);
if (fd == -1) {
ret = fd;
fprintf(stderr, "failed to open %s\n", path);
goto err_disable_chip;
}
ret = read(fd, dev_name, sizeof(dev_name) - 1);
close(fd);
if (ret == -1) {
fprintf(stderr, "failed to read %s\n", path);
goto err_disable_chip;
}
dev_name[ret] = '\0';
if (ret && dev_name[ret - 1] == '\n')
dev_name[ret - 1] = '\0';
snprintf(path, sizeof(path), "/dev/%s", dev_name);
fd = open(path, O_RDWR);
if (fd == -1) {
ret = fd;
fprintf(stderr, "failed to open %s\n", path);
goto err_disable_chip;
}
return fd;
err_disable_chip:
disable_chip(chip_name);
err_remove_bank:
remove_bank(chip_name, bank_name);
err_remove_chip:
remove_chip(chip_name);
return ret;
}
static void close_chip(const char *chip_name, const char *bank_name)
{
disable_chip(chip_name);
remove_bank(chip_name, bank_name);
remove_chip(chip_name);
}
static int test_poll(int fd)
{
struct pollfd pfds;
pfds.fd = fd;
pfds.events = POLLIN;
pfds.revents = 0;
if (poll(&pfds, 1, 0) == -1)
return -1;
return (pfds.revents & ~(POLLHUP | POLLERR)) ? -1 : 0;
}
static int test_read(int fd)
{
char data;
if (read(fd, &data, 1) == -1 && errno == ENODEV)
return 0;
return -1;
}
static int test_ioctl(int fd)
{
if (ioctl(fd, 0, NULL) == -1 && errno == ENODEV)
return 0;
return -1;
}
int main(int argc, char **argv)
{
int cfd, fd, ret;
int (*test_func)(int);
if (argc != 3) {
print_usage();
return EXIT_FAILURE;
}
if (strcmp(argv[1], "chip") == 0 ||
strcmp(argv[1], "event") == 0 ||
strcmp(argv[1], "req") == 0) {
if (strcmp(argv[2], "poll") &&
strcmp(argv[2], "read") &&
strcmp(argv[2], "ioctl")) {
fprintf(stderr, "unknown command: %s\n", argv[2]);
return EXIT_FAILURE;
}
} else if (strcmp(argv[1], "handle") == 0) {
if (strcmp(argv[2], "ioctl")) {
fprintf(stderr, "unknown command: %s\n", argv[2]);
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "unknown command: %s\n", argv[1]);
return EXIT_FAILURE;
}
if (strcmp(argv[2], "poll") == 0)
test_func = test_poll;
else if (strcmp(argv[2], "read") == 0)
test_func = test_read;
else /* strcmp(argv[2], "ioctl") == 0 */
test_func = test_ioctl;
cfd = open_chip("chip", "bank");
if (cfd == -1) {
fprintf(stderr, "failed to open chip\n");
return EXIT_FAILURE;
}
/* Step 1: Hold a FD to the test target. */
if (strcmp(argv[1], "chip") == 0) {
fd = cfd;
} else if (strcmp(argv[1], "handle") == 0) {
struct gpiohandle_request req = {0};
req.lines = 1;
if (ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get handle FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
} else if (strcmp(argv[1], "event") == 0) {
struct gpioevent_request req = {0};
if (ioctl(cfd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get event FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
} else { /* strcmp(argv[1], "req") == 0 */
struct gpio_v2_line_request req = {0};
req.num_lines = 1;
if (ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get req FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
}
/* Step 2: Free the chip. */
close_chip("chip", "bank");
/* Step 3: Access the dangling FD to trigger UAF. */
ret = test_func(fd);
close(fd);
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
err_close_chip:
close(cfd);
close_chip("chip", "bank");
return EXIT_FAILURE;
}
+63
View File
@@ -0,0 +1,63 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright 2026 Google LLC
BASE_DIR=`dirname $0`
MODULE="gpio-cdev-uaf"
fail() {
echo "$*" >&2
echo "GPIO $MODULE test FAIL"
exit 1
}
skip() {
echo "$*" >&2
echo "GPIO $MODULE test SKIP"
exit 4
}
# Load the gpio-sim module. This will pull in configfs if needed too.
modprobe gpio-sim || skip "unable to load the gpio-sim module"
# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed.
for _ in `seq 5`; do
mountpoint -q /sys/kernel/config && break
mount -t configfs none /sys/kernel/config
sleep 0.1
done
mountpoint -q /sys/kernel/config || \
skip "configfs not mounted at /sys/kernel/config"
echo "1. GPIO"
echo "1.1. poll"
$BASE_DIR/gpio-cdev-uaf chip poll || fail "failed to test chip poll"
echo "1.2. read"
$BASE_DIR/gpio-cdev-uaf chip read || fail "failed to test chip read"
echo "1.3. ioctl"
$BASE_DIR/gpio-cdev-uaf chip ioctl || fail "failed to test chip ioctl"
echo "2. linehandle"
echo "2.1. ioctl"
$BASE_DIR/gpio-cdev-uaf handle ioctl || fail "failed to test handle ioctl"
echo "3. lineevent"
echo "3.1. read"
$BASE_DIR/gpio-cdev-uaf event read || fail "failed to test event read"
echo "3.2. poll"
$BASE_DIR/gpio-cdev-uaf event poll || fail "failed to test event poll"
echo "3.3. ioctl"
$BASE_DIR/gpio-cdev-uaf event ioctl || fail "failed to test event ioctl"
echo "4. linereq"
echo "4.1. read"
$BASE_DIR/gpio-cdev-uaf req read || fail "failed to test req read"
echo "4.2. poll"
$BASE_DIR/gpio-cdev-uaf req poll || fail "failed to test req poll"
echo "4.3. ioctl"
$BASE_DIR/gpio-cdev-uaf req ioctl || fail "failed to test req ioctl"
echo "GPIO $MODULE test PASS"
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPIO character device helper for reading chip information.
*
* Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
*/
#include <fcntl.h>
#include <linux/gpio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
static void print_usage(void)
{
printf("usage:\n");
printf(" gpio-chip-info <chip path> [name|label|num-lines]\n");
}
int main(int argc, char **argv)
{
struct gpiochip_info info;
int fd, ret;
if (argc != 3) {
print_usage();
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("unable to open the GPIO chip");
return EXIT_FAILURE;
}
memset(&info, 0, sizeof(info));
ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if (ret) {
perror("chip info ioctl failed");
return EXIT_FAILURE;
}
if (strcmp(argv[2], "name") == 0) {
printf("%s\n", info.name);
} else if (strcmp(argv[2], "label") == 0) {
printf("%s\n", info.label);
} else if (strcmp(argv[2], "num-lines") == 0) {
printf("%u\n", info.lines);
} else {
fprintf(stderr, "unknown command: %s\n", argv[2]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPIO character device helper for reading line names.
*
* Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
*/
#include <fcntl.h>
#include <linux/gpio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
static void print_usage(void)
{
printf("usage:\n");
printf(" gpio-line-name <chip path> <line offset>\n");
}
int main(int argc, char **argv)
{
struct gpio_v2_line_info info;
int fd, ret;
char *endp;
if (argc != 3) {
print_usage();
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("unable to open the GPIO chip");
return EXIT_FAILURE;
}
memset(&info, 0, sizeof(info));
info.offset = strtoul(argv[2], &endp, 10);
if (*endp != '\0') {
print_usage();
return EXIT_FAILURE;
}
ret = ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, &info);
if (ret) {
perror("line info ioctl failed");
return EXIT_FAILURE;
}
printf("%s\n", info.name);
return EXIT_SUCCESS;
}
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0
/*
* GPIO mockup cdev test helper
*
* Copyright (C) 2020 Kent Gibson
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#define CONSUMER "gpio-mockup-cdev"
static int request_line_v2(int cfd, unsigned int offset,
uint64_t flags, unsigned int val)
{
struct gpio_v2_line_request req;
int ret;
memset(&req, 0, sizeof(req));
req.num_lines = 1;
req.offsets[0] = offset;
req.config.flags = flags;
strcpy(req.consumer, CONSUMER);
if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
req.config.num_attrs = 1;
req.config.attrs[0].mask = 1;
req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
if (val)
req.config.attrs[0].attr.values = 1;
}
ret = ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req);
if (ret == -1)
return -errno;
return req.fd;
}
static int get_value_v2(int lfd)
{
struct gpio_v2_line_values vals;
int ret;
memset(&vals, 0, sizeof(vals));
vals.mask = 1;
ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals);
if (ret == -1)
return -errno;
return vals.bits & 0x1;
}
static int request_line_v1(int cfd, unsigned int offset,
uint32_t flags, unsigned int val)
{
struct gpiohandle_request req;
int ret;
memset(&req, 0, sizeof(req));
req.lines = 1;
req.lineoffsets[0] = offset;
req.flags = flags;
strcpy(req.consumer_label, CONSUMER);
if (flags & GPIOHANDLE_REQUEST_OUTPUT)
req.default_values[0] = val;
ret = ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if (ret == -1)
return -errno;
return req.fd;
}
static int get_value_v1(int lfd)
{
struct gpiohandle_data vals;
int ret;
memset(&vals, 0, sizeof(vals));
ret = ioctl(lfd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &vals);
if (ret == -1)
return -errno;
return vals.values[0];
}
static void usage(char *prog)
{
printf("Usage: %s [-l] [-b <bias>] [-s <value>] [-u <uAPI>] <gpiochip> <offset>\n", prog);
printf(" -b: set line bias to one of pull-down, pull-up, disabled\n");
printf(" (default is to leave bias unchanged):\n");
printf(" -l: set line active low (default is active high)\n");
printf(" -s: set line value (default is to get line value)\n");
printf(" -u: uAPI version to use (default is 2)\n");
exit(-1);
}
static int wait_signal(void)
{
int sig;
sigset_t wset;
sigemptyset(&wset);
sigaddset(&wset, SIGHUP);
sigaddset(&wset, SIGINT);
sigaddset(&wset, SIGTERM);
sigwait(&wset, &sig);
return sig;
}
int main(int argc, char *argv[])
{
char *chip;
int opt, ret, cfd, lfd;
unsigned int offset, val = 0, abiv;
uint32_t flags_v1;
uint64_t flags_v2;
abiv = 2;
ret = 0;
flags_v1 = GPIOHANDLE_REQUEST_INPUT;
flags_v2 = GPIO_V2_LINE_FLAG_INPUT;
while ((opt = getopt(argc, argv, "lb:s:u:")) != -1) {
switch (opt) {
case 'l':
flags_v1 |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
flags_v2 |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
break;
case 'b':
if (strcmp("pull-up", optarg) == 0) {
flags_v1 |= GPIOHANDLE_REQUEST_BIAS_PULL_UP;
flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
} else if (strcmp("pull-down", optarg) == 0) {
flags_v1 |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN;
flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
} else if (strcmp("disabled", optarg) == 0) {
flags_v1 |= GPIOHANDLE_REQUEST_BIAS_DISABLE;
flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
}
break;
case 's':
val = atoi(optarg);
flags_v1 &= ~GPIOHANDLE_REQUEST_INPUT;
flags_v1 |= GPIOHANDLE_REQUEST_OUTPUT;
flags_v2 &= ~GPIO_V2_LINE_FLAG_INPUT;
flags_v2 |= GPIO_V2_LINE_FLAG_OUTPUT;
break;
case 'u':
abiv = atoi(optarg);
break;
default:
usage(argv[0]);
}
}
if (argc < optind + 2)
usage(argv[0]);
chip = argv[optind];
offset = atoi(argv[optind + 1]);
cfd = open(chip, 0);
if (cfd == -1) {
fprintf(stderr, "Failed to open %s: %s\n", chip, strerror(errno));
return -errno;
}
if (abiv == 1)
lfd = request_line_v1(cfd, offset, flags_v1, val);
else
lfd = request_line_v2(cfd, offset, flags_v2, val);
close(cfd);
if (lfd < 0) {
fprintf(stderr, "Failed to request %s:%d: %s\n", chip, offset, strerror(-lfd));
return lfd;
}
if (flags_v2 & GPIO_V2_LINE_FLAG_OUTPUT) {
wait_signal();
} else {
if (abiv == 1)
ret = get_value_v1(lfd);
else
ret = get_value_v2(lfd);
}
close(lfd);
return ret;
}
@@ -0,0 +1,77 @@
# SPDX-License-Identifier: GPL-2.0
# Overrides functions in gpio-mockup.sh to test using the GPIO SYSFS uAPI
SYSFS=`grep -w sysfs /proc/mounts | cut -f2 -d' '`
[ -d "$SYSFS" ] || skip "sysfs is not mounted"
GPIO_SYSFS="${SYSFS}/class/gpio"
[ -d "$GPIO_SYSFS" ] || skip "CONFIG_GPIO_SYSFS is not selected"
PLATFORM_SYSFS=$SYSFS/devices/platform
sysfs_nr=
sysfs_ldir=
# determine the sysfs GPIO number given the $chip and $offset
# e.g. gpiochip1:32
find_sysfs_nr()
{
# e.g. /sys/devices/platform/gpio-mockup.1/gpiochip1
local platform=$(find $PLATFORM_SYSFS -mindepth 2 -maxdepth 2 -type d -name $chip)
[ "$platform" ] || fail "can't find platform of $chip"
# e.g. /sys/devices/platform/gpio-mockup.1/gpio/gpiochip508/base
local base=$(find ${platform%/*}/gpio/ -mindepth 2 -maxdepth 2 -type f -name base)
[ "$base" ] || fail "can't find base of $chip"
sysfs_nr=$(($(< "$base") + $offset))
sysfs_ldir="$GPIO_SYSFS/gpio$sysfs_nr"
}
acquire_line()
{
[ "$sysfs_nr" ] && return
find_sysfs_nr
echo "$sysfs_nr" > "$GPIO_SYSFS/export"
}
# The helpers being overridden...
get_line()
{
[ -e "$sysfs_ldir/value" ] && echo $(< "$sysfs_ldir/value")
}
set_line()
{
acquire_line
for option in $*; do
case $option in
active-high)
echo 0 > "$sysfs_ldir/active_low"
;;
active-low)
echo 1 > "$sysfs_ldir/active_low"
;;
input)
echo "in" > "$sysfs_ldir/direction"
;;
0)
echo "out" > "$sysfs_ldir/direction"
echo 0 > "$sysfs_ldir/value"
;;
1)
echo "out" > "$sysfs_ldir/direction"
echo 1 > "$sysfs_ldir/value"
;;
esac
done
}
release_line()
{
[ "$sysfs_nr" ] || return 0
echo "$sysfs_nr" > "$GPIO_SYSFS/unexport"
sysfs_nr=
sysfs_ldir=
}
+400
View File
@@ -0,0 +1,400 @@
#!/bin/bash -efu
# SPDX-License-Identifier: GPL-2.0
#exit status
#0: success
#1: fail
#4: skip test - including run as non-root user
BASE=${0%/*}
DEBUGFS=
GPIO_DEBUGFS=
dev_type="cdev"
module="gpio-mockup"
verbose=
full_test=
random=
uapi_opt=
active_opt=
bias_opt=
line_set_pid=
# Kselftest return codes
ksft_fail=1
ksft_skip=4
usage()
{
echo "Usage:"
echo "$0 [-frv] [-t type]"
echo "-f: full test (minimal set run by default)"
echo "-r: test random lines as well as fence posts"
echo "-t: interface type:"
echo " cdev (character device ABI) - default"
echo " cdev_v1 (deprecated character device ABI)"
echo " sysfs (deprecated SYSFS ABI)"
echo "-v: verbose progress reporting"
exit $ksft_fail
}
skip()
{
echo "$*" >&2
echo "GPIO $module test SKIP"
exit $ksft_skip
}
prerequisite()
{
[ $(id -u) -eq 0 ] || skip "must be run as root"
DEBUGFS=$(grep -w debugfs /proc/mounts | cut -f2 -d' ')
[ -d "$DEBUGFS" ] || skip "debugfs is not mounted"
GPIO_DEBUGFS=$DEBUGFS/$module
}
remove_module()
{
modprobe -r -q $module
}
cleanup()
{
set +e
release_line
remove_module
jobs -p | xargs -r kill > /dev/null 2>&1
}
fail()
{
echo "test failed: $*" >&2
echo "GPIO $module test FAIL"
exit $ksft_fail
}
try_insert_module()
{
modprobe -q $module "$1" || fail "insert $module failed with error $?"
}
log()
{
[ -z "$verbose" ] || echo "$*"
}
# The following line helpers, release_Line, get_line and set_line, all
# make use of the global $chip and $offset variables.
#
# This implementation drives the GPIO character device (cdev) uAPI.
# Other implementations may override these to test different uAPIs.
# Release any resources related to the line
release_line()
{
[ "$line_set_pid" ] && kill $line_set_pid && wait $line_set_pid || true
line_set_pid=
}
# Read the current value of the line
get_line()
{
release_line
local cdev_opts=${uapi_opt}${active_opt}
$BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset
echo $?
}
# Set the state of the line
#
# Changes to line configuration are provided as parameters.
# The line is assumed to be an output if the line value 0 or 1 is
# specified, else an input.
set_line()
{
local val=
release_line
# parse config options...
for option in $*; do
case $option in
active-low)
active_opt="-l "
;;
active-high)
active_opt=
;;
bias-none)
bias_opt=
;;
pull-down)
bias_opt="-bpull-down "
;;
pull-up)
bias_opt="-bpull-up "
;;
0)
val=0
;;
1)
val=1
;;
esac
done
local cdev_opts=${uapi_opt}${active_opt}
if [ "$val" ]; then
$BASE/gpio-mockup-cdev $cdev_opts -s$val /dev/$chip $offset &
# failure to set is detected by reading mockup and toggling values
line_set_pid=$!
# allow for gpio-mockup-cdev to launch and request line
# (there is limited value in checking if line has been requested)
sleep 0.01
elif [ "$bias_opt" ]; then
cdev_opts=${cdev_opts}${bias_opt}
$BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset || true
fi
}
assert_line()
{
local val
# don't need any retry here as set_mock allows for propagation
val=$(get_line)
[ "$val" = "$1" ] || fail "line value is ${val:-empty} when $1 was expected"
}
# The following mockup helpers all make use of the $mock_line
assert_mock()
{
local backoff_wait=10
local retry=0
local val
# retry allows for set propagation from uAPI to mockup
while true; do
val=$(< $mock_line)
[ "$val" = "$1" ] && break
retry=$((retry + 1))
[ $retry -lt 5 ] || fail "mockup $mock_line value ${val:-empty} when $1 expected"
sleep $(printf "%0.2f" $((backoff_wait))e-3)
backoff_wait=$((backoff_wait * 2))
done
}
set_mock()
{
echo "$1" > $mock_line
# allow for set propagation - so we won't be in a race with set_line
assert_mock "$1"
}
# test the functionality of a line
#
# The line is set from the mockup side and is read from the userspace side
# (input), and is set from the userspace side and is read from the mockup side
# (output).
#
# Setting the mockup pull using the userspace interface bias settings is
# tested where supported by the userspace interface (cdev).
test_line()
{
chip=$1
offset=$2
log "test_line $chip $offset"
mock_line=$GPIO_DEBUGFS/$chip/$offset
[ -e "$mock_line" ] || fail "missing line $chip:$offset"
# test input active-high
set_mock 1
set_line input active-high
assert_line 1
set_mock 0
assert_line 0
set_mock 1
assert_line 1
if [ "$full_test" ]; then
if [ "$dev_type" != "sysfs" ]; then
# test pulls
set_mock 0
set_line input pull-up
assert_line 1
set_mock 0
assert_line 0
set_mock 1
set_line input pull-down
assert_line 0
set_mock 1
assert_line 1
set_line bias-none
fi
# test input active-low
set_mock 0
set_line active-low
assert_line 1
set_mock 1
assert_line 0
set_mock 0
assert_line 1
# test output active-high
set_mock 1
set_line active-high 0
assert_mock 0
set_line 1
assert_mock 1
set_line 0
assert_mock 0
fi
# test output active-low
set_mock 0
set_line active-low 0
assert_mock 1
set_line 1
assert_mock 0
set_line 0
assert_mock 1
release_line
}
test_no_line()
{
log test_no_line "$*"
[ ! -e "$GPIO_DEBUGFS/$1/$2" ] || fail "unexpected line $1:$2"
}
# Load the module and check that the expected number of gpiochips, with the
# expected number of lines, are created and are functional.
#
# $1 is the gpio_mockup_ranges parameter for the module
# The remaining parameters are the number of lines, n, expected for each of
# the gpiochips expected to be created.
#
# For each gpiochip the fence post lines, 0 and n-1, are tested, and the
# line on the far side of the fence post, n, is tested to not exist.
#
# If the $random flag is set then a random line in the middle of the
# gpiochip is tested as well.
insmod_test()
{
local ranges=
local gc=
local width=
[ "${1:-}" ] || fail "missing ranges"
ranges=$1 ; shift
try_insert_module "gpio_mockup_ranges=$ranges"
log "GPIO $module test with ranges: <$ranges>:"
# e.g. /sys/kernel/debug/gpio-mockup/gpiochip1
gpiochip=$(find "$DEBUGFS/$module/" -name gpiochip* -type d | sort)
for chip in $gpiochip; do
gc=${chip##*/}
[ "${1:-}" ] || fail "unexpected chip - $gc"
width=$1 ; shift
test_line $gc 0
if [ "$random" -a $width -gt 2 ]; then
test_line $gc $((RANDOM % ($width - 2) + 1))
fi
test_line $gc $(($width - 1))
test_no_line $gc $width
done
[ "${1:-}" ] && fail "missing expected chip of width $1"
remove_module || fail "failed to remove module with error $?"
}
while getopts ":frvt:" opt; do
case $opt in
f)
full_test=true
;;
r)
random=true
;;
t)
dev_type=$OPTARG
;;
v)
verbose=true
;;
*)
usage
;;
esac
done
shift $((OPTIND - 1))
[ "${1:-}" ] && fail "unknown argument '$1'"
prerequisite
trap 'exit $ksft_fail' SIGTERM SIGINT
trap cleanup EXIT
case "$dev_type" in
sysfs)
source $BASE/gpio-mockup-sysfs.sh
echo "WARNING: gpio sysfs ABI is deprecated."
;;
cdev_v1)
echo "WARNING: gpio cdev ABI v1 is deprecated."
uapi_opt="-u1 "
;;
cdev)
;;
*)
fail "unknown interface type: $dev_type"
;;
esac
remove_module || fail "can't remove existing $module module"
# manual gpio allocation tests fail if a physical chip already exists
[ "$full_test" -a -e "/dev/gpiochip0" ] && skip "full tests conflict with gpiochip0"
echo "1. Module load tests"
echo "1.1. dynamic allocation of gpio"
insmod_test "-1,32" 32
insmod_test "-1,23,-1,32" 23 32
insmod_test "-1,23,-1,26,-1,32" 23 26 32
if [ "$full_test" ]; then
echo "1.2. manual allocation of gpio"
insmod_test "0,32" 32
insmod_test "0,32,32,60" 32 28
insmod_test "0,32,40,64,64,96" 32 24 32
echo "1.3. dynamic and manual allocation of gpio"
insmod_test "-1,32,32,62" 32 30
insmod_test "-1,22,-1,23,0,24,32,64" 22 23 24 32
insmod_test "-1,32,32,60,-1,29" 32 28 29
insmod_test "-1,32,40,64,-1,5" 32 24 5
insmod_test "0,32,32,44,-1,22,-1,31" 32 12 22 31
fi
echo "2. Module load error tests"
echo "2.1 no lines defined"
insmod_test "0,0"
if [ "$full_test" ]; then
echo "2.2 ignore range overlap"
insmod_test "0,32,0,1" 32
insmod_test "0,32,1,5" 32
insmod_test "0,32,30,35" 32
insmod_test "0,32,31,32" 32
insmod_test "10,32,30,35" 22
insmod_test "10,32,9,14" 22
insmod_test "0,32,20,21,40,56" 32 16
insmod_test "0,32,32,64,32,40" 32 32
insmod_test "0,32,32,64,36,37" 32 32
insmod_test "0,32,35,64,34,36" 32 29
insmod_test "0,30,35,64,35,45" 30 29
insmod_test "0,32,40,56,30,33" 32 16
insmod_test "0,32,40,56,30,41" 32 16
insmod_test "0,32,40,56,39,45" 32 16
fi
echo "GPIO $module test PASS"
+418
View File
@@ -0,0 +1,418 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
BASE_DIR=`dirname $0`
CONFIGFS_DIR="/sys/kernel/config/gpio-sim"
MODULE="gpio-sim"
fail() {
echo "$*" >&2
echo "GPIO $MODULE test FAIL"
exit 1
}
skip() {
echo "$*" >&2
echo "GPIO $MODULE test SKIP"
exit 4
}
remove_chip() {
local CHIP=$1
for FILE in $CONFIGFS_DIR/$CHIP/*; do
BANK=`basename $FILE`
if [ "$BANK" = "live" -o "$BANK" = "dev_name" ]; then
continue
fi
LINES=`ls $CONFIGFS_DIR/$CHIP/$BANK/ | grep -E ^line`
if [ "$?" = 0 ]; then
for LINE in $LINES; do
if [ -e $CONFIGFS_DIR/$CHIP/$BANK/$LINE/hog ]; then
rmdir $CONFIGFS_DIR/$CHIP/$BANK/$LINE/hog || \
fail "Unable to remove the hog"
fi
rmdir $CONFIGFS_DIR/$CHIP/$BANK/$LINE || \
fail "Unable to remove the line"
done
fi
rmdir $CONFIGFS_DIR/$CHIP/$BANK
done
rmdir $CONFIGFS_DIR/$CHIP || fail "Unable to remove the chip"
}
create_chip() {
local CHIP=$1
mkdir $CONFIGFS_DIR/$CHIP
}
create_bank() {
local CHIP=$1
local BANK=$2
mkdir $CONFIGFS_DIR/$CHIP/$BANK
}
set_label() {
local CHIP=$1
local BANK=$2
local LABEL=$3
echo $LABEL > $CONFIGFS_DIR/$CHIP/$BANK/label || fail "Unable to set the chip label"
}
set_num_lines() {
local CHIP=$1
local BANK=$2
local NUM_LINES=$3
echo $NUM_LINES > $CONFIGFS_DIR/$CHIP/$BANK/num_lines || \
fail "Unable to set the number of lines"
}
set_line_name() {
local CHIP=$1
local BANK=$2
local OFFSET=$3
local NAME=$4
local LINE_DIR=$CONFIGFS_DIR/$CHIP/$BANK/line$OFFSET
test -d $LINE_DIR || mkdir $LINE_DIR
echo $NAME > $LINE_DIR/name || fail "Unable to set the line name"
}
enable_chip() {
local CHIP=$1
echo 1 > $CONFIGFS_DIR/$CHIP/live || fail "Unable to enable the chip"
}
disable_chip() {
local CHIP=$1
echo 0 > $CONFIGFS_DIR/$CHIP/live || fail "Unable to disable the chip"
}
configfs_cleanup() {
for CHIP in `ls $CONFIGFS_DIR/`; do
disable_chip $CHIP
remove_chip $CHIP
done
}
configfs_chip_name() {
local CHIP=$1
local BANK=$2
cat $CONFIGFS_DIR/$CHIP/$BANK/chip_name 2> /dev/null || \
fail "unable to read the chip name from configfs"
}
configfs_dev_name() {
local CHIP=$1
cat $CONFIGFS_DIR/$CHIP/dev_name 2> /dev/null || \
fail "unable to read the device name from configfs"
}
get_chip_num_lines() {
local CHIP=$1
local BANK=$2
$BASE_DIR/gpio-chip-info /dev/`configfs_chip_name $CHIP $BANK` num-lines || \
fail "unable to read the number of lines from the character device"
}
get_chip_label() {
local CHIP=$1
local BANK=$2
$BASE_DIR/gpio-chip-info /dev/`configfs_chip_name $CHIP $BANK` label || \
fail "unable to read the chip label from the character device"
}
get_line_name() {
local CHIP=$1
local BANK=$2
local OFFSET=$3
$BASE_DIR/gpio-line-name /dev/`configfs_chip_name $CHIP $BANK` $OFFSET || \
fail "unable to read the line name from the character device"
}
sysfs_set_pull() {
local DEV=$1
local BANK=$2
local OFFSET=$3
local PULL=$4
local DEVNAME=`configfs_dev_name $DEV`
local CHIPNAME=`configfs_chip_name $DEV $BANK`
local SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull"
echo $PULL > $SYSFS_PATH || fail "Unable to set line pull in sysfs"
}
# Load the gpio-sim module. This will pull in configfs if needed too.
modprobe gpio-sim || skip "unable to load the gpio-sim module"
# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed.
for IDX in `seq 5`; do
if [ "$IDX" -eq "5" ]; then
skip "configfs not mounted at /sys/kernel/config"
fi
mountpoint -q /sys/kernel/config && break
sleep 0.1
done
# If the module was already loaded: remove all previous chips
configfs_cleanup
trap "exit 1" SIGTERM SIGINT
trap configfs_cleanup EXIT
echo "1. chip_name and dev_name attributes"
echo "1.1. Chip name is communicated to user"
create_chip chip
create_bank chip bank
enable_chip chip
test -n `cat $CONFIGFS_DIR/chip/bank/chip_name` || fail "chip_name doesn't work"
disable_chip chip
remove_chip chip
echo "1.2. chip_name returns 'none' if the chip is still pending"
create_chip chip
create_bank chip bank
test "`cat $CONFIGFS_DIR/chip/bank/chip_name`" = "none" || \
fail "chip_name doesn't return 'none' for a pending chip"
remove_chip chip
echo "1.3. Device name is communicated to user"
create_chip chip
create_bank chip bank
enable_chip chip
test -n `cat $CONFIGFS_DIR/chip/dev_name` || fail "dev_name doesn't work"
disable_chip chip
remove_chip chip
echo "2. Creating and configuring simulated chips"
echo "2.1. Default number of lines is 1"
create_chip chip
create_bank chip bank
enable_chip chip
test "`get_chip_num_lines chip bank`" = "1" || fail "default number of lines is not 1"
disable_chip chip
remove_chip chip
echo "2.2. Number of lines can be specified"
create_chip chip
create_bank chip bank
set_num_lines chip bank 16
enable_chip chip
test "`get_chip_num_lines chip bank`" = "16" || fail "number of lines is not 16"
disable_chip chip
remove_chip chip
echo "2.3. Label can be set"
create_chip chip
create_bank chip bank
set_label chip bank foobar
enable_chip chip
test "`get_chip_label chip bank`" = "foobar" || fail "label is incorrect"
disable_chip chip
remove_chip chip
echo "2.4. Label can be left empty"
create_chip chip
create_bank chip bank
enable_chip chip
test -z "`cat $CONFIGFS_DIR/chip/bank/label`" || fail "label is not empty"
disable_chip chip
remove_chip chip
echo "2.5. Line names can be configured"
create_chip chip
create_bank chip bank
set_num_lines chip bank 16
set_line_name chip bank 0 foo
set_line_name chip bank 2 bar
enable_chip chip
test "`get_line_name chip bank 0`" = "foo" || fail "line name is incorrect"
test "`get_line_name chip bank 2`" = "bar" || fail "line name is incorrect"
disable_chip chip
remove_chip chip
echo "2.6. Line config can remain unused if offset is greater than number of lines"
create_chip chip
create_bank chip bank
set_num_lines chip bank 2
set_line_name chip bank 5 foobar
enable_chip chip
test "`get_line_name chip bank 0`" = "" || fail "line name is incorrect"
test "`get_line_name chip bank 1`" = "" || fail "line name is incorrect"
disable_chip chip
remove_chip chip
echo "2.7. Line configfs directory names are sanitized"
create_chip chip
create_bank chip bank
mkdir $CONFIGFS_DIR/chip/bank/line12foobar 2> /dev/null && \
fail "invalid configfs line name accepted"
mkdir $CONFIGFS_DIR/chip/bank/line_no_offset 2> /dev/null && \
fail "invalid configfs line name accepted"
remove_chip chip
echo "2.8. Multiple chips can be created"
CHIPS="chip0 chip1 chip2"
for CHIP in $CHIPS; do
create_chip $CHIP
create_bank $CHIP bank
enable_chip $CHIP
done
for CHIP in $CHIPS; do
disable_chip $CHIP
remove_chip $CHIP
done
echo "2.9. Can't modify settings when chip is live"
create_chip chip
create_bank chip bank
enable_chip chip
echo foobar > $CONFIGFS_DIR/chip/bank/label 2> /dev/null && \
fail "Setting label of a live chip should fail"
echo 8 > $CONFIGFS_DIR/chip/bank/num_lines 2> /dev/null && \
fail "Setting number of lines of a live chip should fail"
disable_chip chip
remove_chip chip
echo "2.10. Can't create line items when chip is live"
create_chip chip
create_bank chip bank
enable_chip chip
mkdir $CONFIGFS_DIR/chip/bank/line0 2> /dev/null && fail "Creating line item should fail"
disable_chip chip
remove_chip chip
echo "2.11. Probe errors are propagated to user-space"
create_chip chip
create_bank chip bank
set_num_lines chip bank 99999
echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Probe error was not propagated"
remove_chip chip
echo "2.12. Cannot enable a chip without any GPIO banks"
create_chip chip
echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Chip enabled without any GPIO banks"
remove_chip chip
echo "2.13. Duplicate chip labels are not allowed"
create_chip chip
create_bank chip bank0
set_label chip bank0 foobar
create_bank chip bank1
set_label chip bank1 foobar
echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Duplicate chip labels were not rejected"
remove_chip chip
echo "2.14. Lines can be hogged"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
mkdir -p $CONFIGFS_DIR/chip/bank/line4/hog
enable_chip chip
$BASE_DIR/gpio-mockup-cdev -s 1 /dev/`configfs_chip_name chip bank` 4 2> /dev/null && \
fail "Setting the value of a hogged line shouldn't succeed"
disable_chip chip
remove_chip chip
echo "3. Controlling simulated chips"
echo "3.1. Pull can be set over sysfs"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
enable_chip chip
sysfs_set_pull chip bank 0 pull-up
$BASE_DIR/gpio-mockup-cdev /dev/`configfs_chip_name chip bank` 0
test "$?" = "1" || fail "pull set incorrectly"
sysfs_set_pull chip bank 0 pull-down
$BASE_DIR/gpio-mockup-cdev /dev/`configfs_chip_name chip bank` 1
test "$?" = "0" || fail "pull set incorrectly"
disable_chip chip
remove_chip chip
echo "3.2. Pull can be read from sysfs"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
enable_chip chip
DEVNAME=`configfs_dev_name chip`
CHIPNAME=`configfs_chip_name chip bank`
SYSFS_PATH=/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/pull
test `cat $SYSFS_PATH` = "pull-down" || fail "reading the pull failed"
sysfs_set_pull chip bank 0 pull-up
test `cat $SYSFS_PATH` = "pull-up" || fail "reading the pull failed"
disable_chip chip
remove_chip chip
echo "3.3. Incorrect input in sysfs is rejected"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
enable_chip chip
DEVNAME=`configfs_dev_name chip`
CHIPNAME=`configfs_chip_name chip bank`
SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/pull"
echo foobar > $SYSFS_PATH 2> /dev/null && fail "invalid input not detected"
disable_chip chip
remove_chip chip
echo "3.4. Can't write to value"
create_chip chip
create_bank chip bank
enable_chip chip
DEVNAME=`configfs_dev_name chip`
CHIPNAME=`configfs_chip_name chip bank`
SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value"
echo 1 > $SYSFS_PATH 2> /dev/null && fail "writing to 'value' succeeded unexpectedly"
disable_chip chip
remove_chip chip
echo "4. Simulated GPIO chips are functional"
echo "4.1. Values can be read from sysfs"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
enable_chip chip
DEVNAME=`configfs_dev_name chip`
CHIPNAME=`configfs_chip_name chip bank`
SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value"
test `cat $SYSFS_PATH` = "0" || fail "incorrect value read from sysfs"
$BASE_DIR/gpio-mockup-cdev -s 1 /dev/`configfs_chip_name chip bank` 0 &
sleep 0.1 # FIXME Any better way?
test `cat $SYSFS_PATH` = "1" || fail "incorrect value read from sysfs"
kill $!
disable_chip chip
remove_chip chip
echo "4.2. Bias settings work correctly"
create_chip chip
create_bank chip bank
set_num_lines chip bank 8
enable_chip chip
DEVNAME=`configfs_dev_name chip`
CHIPNAME=`configfs_chip_name chip bank`
SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value"
$BASE_DIR/gpio-mockup-cdev -b pull-up /dev/`configfs_chip_name chip bank` 0
test `cat $SYSFS_PATH` = "1" || fail "bias setting does not work"
disable_chip chip
remove_chip chip
echo "GPIO $MODULE test PASS"