Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions drivers/hwmon/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1252,10 +1252,13 @@ config SENSORS_MACSMC_HWMON_KUNIT_TEST
depends on SENSORS_MACSMC_HWMON && KUNIT=y
default KUNIT_ALL_TESTS
help
Unit tests for the channel numbering of the Apple SMC hwmon driver.
Unit tests for the channel numbering and the manual fan control of
the Apple SMC hwmon driver.
Thermal zones refer to temperature channels by index, so a sensor
that cannot be set up must keep its channel; the tests check that.
No SMC is needed. Only useful for kernel developers.
No SMC is needed: the fan tests model its fan keys and need
MFD_MACSMC_KUNIT_TEST for its read and write stubs. Only useful for
kernel developers.

config SENSORS_MAX1111
tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles"
Expand Down
262 changes: 260 additions & 2 deletions drivers/hwmon/macsmc-hwmon-test.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* KUnit tests for the Apple SMC hwmon driver: channel numbering of the
* temperature sensors, which thermal zones depend on.
* temperature sensors, which thermal zones depend on, and manual fan control.
*
* Included by macsmc-hwmon.c.
*/

#include <kunit/static_stub.h>
#include <kunit/test.h>

/* What macsmc_hwmon_create_sensor() leaves behind for one node. */
Expand Down Expand Up @@ -84,4 +85,261 @@ static struct kunit_suite macsmc_hwmon_test_suite = {
.test_cases = macsmc_hwmon_test_cases,
};

kunit_test_suite(macsmc_hwmon_test_suite);
/*
* Manual fan control against a model of the SMC fan keys. In "locked" firmware
* the fans start in system mode (3) and refuse manual mode until Ftst is 1;
* Ftst back to 0 returns them to system mode. "Legacy" firmware takes the mode
* write directly. The model needs the SMC read/write stubs of
* CONFIG_MFD_MACSMC_KUNIT_TEST.
*/
#define FAN_TEST_F1350 0x44a8c000 /* 1350.0f */
#define FAN_TEST_F2500 0x451c4000 /* 2500.0f */
#define FAN_TEST_F5777 0x45b48800 /* 5777.0f */

struct macsmc_fan_test {
struct apple_smc smc;
struct kunit *test;
struct macsmc_hwmon hwmon;
struct macsmc_hwmon_fan fans[2];
bool legacy;
u8 mode[2];
u32 target[2];
u8 ftst;
int ftst_writes;
int refused;
smc_key fail_key;
};

static int fan_test_index(smc_key key)
{
return ((key >> 16) & 0xff) - '0';
}

static int fan_test_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
{
struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc);
u32 value;
int i = fan_test_index(key);

switch (key & 0xff00ffff) {
case __SMC_KEY('F', 0, 'M', 'd'):
value = t->mode[i];
break;
case __SMC_KEY('F', 0, 'M', 'n'):
value = FAN_TEST_F1350;
break;
case __SMC_KEY('F', 0, 'M', 'x'):
value = FAN_TEST_F5777;
break;
case __SMC_KEY('F', 0, 'T', 'g'):
value = t->target[i];
break;
default:
KUNIT_FAIL(t->test, "unexpected read of %08x", key);
return -EINVAL;
}
memcpy(buf, &value, size);
return size;
}

static int fan_test_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
{
struct macsmc_fan_test *t = container_of(smc, struct macsmc_fan_test, smc);
u32 value = 0;
int i = fan_test_index(key);

memcpy(&value, buf, min(size, sizeof(value)));
if (key == t->fail_key)
return -EIO;

if (key == SMC_KEY(Ftst)) {
t->ftst_writes++;
t->ftst = value;
for (i = 0; i < ARRAY_SIZE(t->mode); i++) {
if (value && t->mode[i] == MACSMC_FAN_MODE_SYSTEM)
t->mode[i] = MACSMC_FAN_MODE_AUTO;
else if (!value)
t->mode[i] = MACSMC_FAN_MODE_SYSTEM;
}
return size;
}

switch (key & 0xff00ffff) {
case __SMC_KEY('F', 0, 'M', 'd'):
if (!t->legacy && t->mode[i] == MACSMC_FAN_MODE_SYSTEM) {
t->refused++;
return -EIO;
}
t->mode[i] = value;
return size;
case __SMC_KEY('F', 0, 'T', 'g'):
t->target[i] = value;
return size;
}
KUNIT_FAIL(t->test, "unexpected write of %08x", key);
return -EINVAL;
}

static void fan_test_key(struct macsmc_hwmon_sensor *sensor, const char *key, u32 type)
{
sensor->macsmc_key = _SMC_KEY(key);
sensor->info.type_code = type;
sensor->info.flags = APPLE_SMC_READABLE | APPLE_SMC_WRITABLE;
}

static int macsmc_fan_test_init(struct kunit *test)
{
static const char *const keys[2][5] = {
{ "F0Ac", "F0Mn", "F0Mx", "F0Tg", "F0Md" },
{ "F1Ac", "F1Mn", "F1Mx", "F1Tg", "F1Md" },
};
const u32 flt = __SMC_KEY('f', 'l', 't', ' '), ui8 = __SMC_KEY('u', 'i', '8', ' ');
struct macsmc_fan_test *t;
int i;

if (!IS_ENABLED(CONFIG_MFD_MACSMC_KUNIT_TEST))
kunit_skip(test, "needs CONFIG_MFD_MACSMC_KUNIT_TEST for the SMC stubs");

t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL);
if (!t)
return -ENOMEM;
t->test = test;
for (i = 0; i < 2; i++) {
fan_test_key(&t->fans[i].now, keys[i][0], flt);
fan_test_key(&t->fans[i].min, keys[i][1], flt);
fan_test_key(&t->fans[i].max, keys[i][2], flt);
fan_test_key(&t->fans[i].set, keys[i][3], flt);
fan_test_key(&t->fans[i].mode, keys[i][4], ui8);
t->mode[i] = MACSMC_FAN_MODE_SYSTEM;
}
t->hwmon.smc = &t->smc;
t->hwmon.fan.fans = t->fans;
t->hwmon.fan.count = 2;
t->hwmon.fan.unlock_key = SMC_KEY(Ftst);
mutex_init(&t->hwmon.fan.lock);
test->priv = t;
fan_control = true;
kunit_activate_static_stub(test, apple_smc_read, fan_test_read);
kunit_activate_static_stub(test, apple_smc_write, fan_test_write);
return 0;
}

static void macsmc_fan_test_exit(struct kunit *test)
{
fan_control = false;
}

static void fan_test_released(struct kunit *test, struct macsmc_fan_test *t)
{
KUNIT_EXPECT_EQ(test, t->ftst, 0);
KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM);
KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_SYSTEM);
KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0);
KUNIT_EXPECT_FALSE(test, t->hwmon.fan.unlocked);
KUNIT_EXPECT_FALSE(test, t->fans[0].manual);
}

/* Locked firmware: unlock on the first manual request, release on auto. */
static void macsmc_fan_test_unlock_and_release(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0);
KUNIT_EXPECT_EQ(test, t->ftst, 1);
KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL);
KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F2500);
KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 1);

/* A second target needs no new unlock. */
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0);
KUNIT_EXPECT_EQ(test, t->ftst_writes, 1);

KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0);
fan_test_released(test, t);
KUNIT_EXPECT_EQ(test, t->ftst_writes, 2);
}

/* Targets are clamped to [min, max]; negative ones are refused. */
static void macsmc_fan_test_clamp(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100), 0);
KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F1350);
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 100000), 0);
KUNIT_EXPECT_EQ(test, t->target[0], FAN_TEST_F5777);
KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, -1), -EINVAL);
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0);
fan_test_released(test, t);
}

/* A failed target write hands the fan straight back. */
static void macsmc_fan_test_target_write_fails(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

t->fail_key = SMC_KEY(F0Tg);
KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO);
fan_test_released(test, t);
}

/* Two manual fans: the unlock is released only with the last one. */
static void macsmc_fan_test_two_fans(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0);
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 1, 2500), 0);
KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 2);
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0);
KUNIT_EXPECT_EQ(test, t->ftst, 1);
KUNIT_EXPECT_EQ(test, t->mode[1], MACSMC_FAN_MODE_MANUAL);
/* Removal, shutdown and suspend all restore through this. */
macsmc_hwmon_fans_restore(&t->hwmon);
fan_test_released(test, t);
}

/* Locked firmware without the unlock key: refused, nothing else written. */
static void macsmc_fan_test_no_unlock_key(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

t->hwmon.fan.unlock_key = 0;
KUNIT_EXPECT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), -EIO);
KUNIT_EXPECT_EQ(test, t->ftst_writes, 0);
KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_SYSTEM);
KUNIT_EXPECT_EQ(test, t->hwmon.fan.manual, 0);
}

/* Firmware that takes the mode directly keeps the mode 0 restore. */
static void macsmc_fan_test_legacy(struct kunit *test)
{
struct macsmc_fan_test *t = test->priv;

t->legacy = true;
t->mode[0] = MACSMC_FAN_MODE_AUTO;
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 2500), 0);
KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_MANUAL);
KUNIT_ASSERT_EQ(test, macsmc_hwmon_set_fan(&t->hwmon, 0, 0), 0);
KUNIT_EXPECT_EQ(test, t->mode[0], MACSMC_FAN_MODE_AUTO);
KUNIT_EXPECT_EQ(test, t->ftst_writes, 0);
KUNIT_EXPECT_EQ(test, t->refused, 0);
}

static struct kunit_case macsmc_fan_test_cases[] = {
KUNIT_CASE(macsmc_fan_test_unlock_and_release),
KUNIT_CASE(macsmc_fan_test_clamp),
KUNIT_CASE(macsmc_fan_test_target_write_fails),
KUNIT_CASE(macsmc_fan_test_two_fans),
KUNIT_CASE(macsmc_fan_test_no_unlock_key),
KUNIT_CASE(macsmc_fan_test_legacy),
{}
};

static struct kunit_suite macsmc_fan_test_suite = {
.name = "macsmc-hwmon-fan",
.init = macsmc_fan_test_init,
.exit = macsmc_fan_test_exit,
.test_cases = macsmc_fan_test_cases,
};
kunit_test_suites(&macsmc_hwmon_test_suite, &macsmc_fan_test_suite);
Loading