linux/drivers/misc/issei/main.c
Alexander Usyskin 7bd4b9991d issei: implement main thread and ham messages
Introduce the main thread and HECI Active Management (HAM)
message handling for the ISSEI (Intel Silicon Security Engine
Interface) subsystem.
The main thread is responsible for managing the reset flow and
processing messages, while the HAM message handling is crucial
for initializing communication with the firmware and managing
clients.

With this implementation, the ISSEI driver is capable of performing
the required initialization and management of communication between
the host and the firmware.

Reviewed-by: Karol Wachowski <karol.wachowski@linux.intel.com>
Co-developed-by: Vitaly Lubart <lubvital@gmail.com>
Signed-off-by: Vitaly Lubart <lubvital@gmail.com>
Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-3-f590038678f9@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026-07-17 15:48:11 +02:00

297 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2023-2026 Intel Corporation */
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include "cdev.h"
#include "fw_client.h"
#include "host_client.h"
#include "ham.h"
#include "issei_dev.h"
static void issei_rst_state_set(struct issei_device *idev, enum issei_rst_state state)
{
idev->rst_state = state;
/* wake up the thread */
if (waitqueue_active(&idev->wait_rst_state))
wake_up(&idev->wait_rst_state);
}
static int issei_reset(struct issei_device *idev)
{
int ret;
idev->ops->irq_clear(idev);
issei_cl_all_disconnect(idev);
issei_fw_cl_remove_all(idev);
/* No need to check for overflow here, the counter is used only for info */
idev->all_reset_count++;
ret = idev->ops->hw_reset(idev, !idev->power_down);
issei_dmam_setup(idev);
if (ret) {
dev_err(&idev->dev, "hw_reset failed ret = %d\n", ret);
return ret;
}
if (idev->power_down) {
dev_dbg(&idev->dev, "powering down: end of reset\n");
issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
return -ENODEV;
}
return 0;
}
static int issei_process_read_msg(struct issei_device *idev)
{
struct issei_dma_data data = {};
int ret;
ret = issei_dma_read(idev, &data);
if (ret)
return ret;
dev_dbg(&idev->dev, "Processing response %u %u %u %u\n", data.fw_id, data.host_id,
data.status, data.length);
if (data.status != HAMS_SUCCESS) {
dev_err(&idev->dev, "Command failed with status 0x%02X", data.status);
kfree(data.buf);
ret = -EIO;
} else {
if (issei_is_ham_rsp(data.fw_id, data.host_id))
ret = issei_ham_process_ham_rsp(idev, data.buf, data.length);
else
ret = issei_cl_read_buf(idev, data.fw_id, data.host_id,
data.buf, data.length);
}
idev->ops->irq_write_generate(idev);
return ret;
}
static int issei_process_write_msg(struct issei_device *idev)
{
if (idev->rst_state != ISSEI_RST_STATE_DONE)
return 0;
return issei_cl_write_from_queue(idev);
}
static int issei_process_thread(void *_dev)
{
long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
struct issei_device *idev = _dev;
int ret;
while (!kthread_should_stop()) {
dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
if (!idev->power_down)
dev_dbg(&idev->dev, "HW not ready, resetting\n");
idev->rst_state = ISSEI_RST_STATE_INIT;
}
if (idev->power_down)
idev->rst_state = ISSEI_RST_STATE_INIT;
WRITE_ONCE(idev->has_data, false);
dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
timeout = MAX_SCHEDULE_TIMEOUT;
ret = 0;
switch (idev->rst_state) {
case ISSEI_RST_STATE_DISABLED:
if (idev->power_down) {
dev_dbg(&idev->dev, "Interrupt in power down?\n");
break;
}
idev->rst_state = ISSEI_RST_STATE_INIT;
fallthrough;
case ISSEI_RST_STATE_INIT:
idev->ops->irq_clear(idev);
idev->ops->irq_sync(idev);
if (!idev->power_down) {
idev->reset_count++;
if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
break;
}
}
ret = issei_reset(idev);
if (idev->power_down) {
dev_dbg(&idev->dev, "Powering down\n");
return 0;
}
if (ret)
break;
idev->rst_state = ISSEI_RST_STATE_HW_READY;
timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
break;
case ISSEI_RST_STATE_HW_READY:
if (!idev->ops->hw_is_ready(idev)) {
dev_dbg(&idev->dev, "HW is not ready?\n");
timeout = old_timeout;
break;
}
dev_dbg(&idev->dev, "HW is ready\n");
idev->ops->hw_reset_release(idev);
idev->ops->host_set_ready(idev);
ret = idev->ops->setup_message_send(idev);
if (ret)
break;
idev->rst_state = ISSEI_RST_STATE_SETUP;
timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
break;
case ISSEI_RST_STATE_SETUP:
ret = idev->ops->setup_message_recv(idev);
if (ret) {
if (ret == -ENODATA) {
ret = 0;
timeout = old_timeout;
}
} else {
timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
ret = issei_ham_send_start_req(idev);
idev->rst_state = ISSEI_RST_STATE_START;
}
break;
case ISSEI_RST_STATE_START:
ret = issei_process_read_msg(idev);
if (!ret) {
timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
} else if (ret == -ENODATA) {
ret = 0;
timeout = old_timeout;
}
break;
case ISSEI_RST_STATE_CLIENT_ENUM:
ret = issei_process_read_msg(idev);
if (ret) {
if (ret == -ENODATA) {
ret = 0;
timeout = old_timeout;
}
} else {
idev->reset_count = 0;
idev->rst_state = ISSEI_RST_STATE_DONE;
dev_dbg(&idev->dev, "Reset finished successfully\n");
}
break;
case ISSEI_RST_STATE_DONE:
ret = issei_process_read_msg(idev);
if (ret != 0 && ret != -ENODATA)
break;
ret = issei_process_write_msg(idev);
break;
}
if (ret) {
dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
idev->rst_state = ISSEI_RST_STATE_INIT;
continue;
}
/*
* Every thread that has data to process sets the 'has_data' flag and
* triggers the wait queue.
* The processing thread, in each loop iteration, resets 'has_data'
* and processes all available data.
*
* After processing, the thread waits for 'has_data' to be set again.
*
* If the wait function times out but 'has_data' becomes 1 before
* the subsequent atomic read check, this is acceptable from a flow
* perspective - the thread will continue processing the data.
*
* The 'has_data' flag cannot become 0 between the wait function and
* the atomic read check, since only this thread is allowed to reset it to 0.
*/
old_timeout = wait_event_interruptible_timeout(idev->wait_has_data,
READ_ONCE(idev->has_data),
timeout);
if (idev->rst_state == ISSEI_RST_STATE_DISABLED)
continue;
if (!READ_ONCE(idev->has_data)) {
dev_warn(&idev->dev, "Timed out at state %d, resetting\n",
idev->rst_state);
idev->rst_state = ISSEI_RST_STATE_INIT;
}
}
return 0;
}
/**
* issei_start - configure HW device and start processing thread.
* @idev: the device structure
*
* Return: 0 on success, < 0 on failure
*/
int issei_start(struct issei_device *idev)
{
int ret;
idev->power_down = false;
ret = issei_dmam_setup(idev);
if (ret)
return ret;
idev->ops->irq_clear(idev);
ret = idev->ops->hw_config(idev);
if (ret)
return ret;
idev->process_thread = kthread_run(issei_process_thread, idev,
"kisseiprocess/%s", dev_name(&idev->dev));
if (IS_ERR(idev->process_thread)) {
ret = PTR_ERR(idev->process_thread);
dev_err(&idev->dev, "unable to create process thread. ret = %d\n", ret);
return ret;
}
issei_poke_process_thread(idev);
return 0;
}
EXPORT_SYMBOL_GPL(issei_start);
/**
* issei_stop - stop interrupts and processing thread.
* @idev: the device structure
*/
void issei_stop(struct issei_device *idev)
{
idev->power_down = true;
idev->ops->irq_clear(idev);
idev->ops->irq_sync(idev);
issei_poke_process_thread(idev);
wait_event_timeout(idev->wait_rst_state,
(idev->rst_state == ISSEI_RST_STATE_DISABLED),
msecs_to_jiffies(ISSEI_STOP_TIMEOUT_MSEC));
kthread_stop(idev->process_thread);
}
EXPORT_SYMBOL_GPL(issei_stop);