summaryrefslogtreecommitdiffstats
path: root/drivers/misc/issei/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/issei/main.c')
-rw-r--r--drivers/misc/issei/main.c296
1 files changed, 296 insertions, 0 deletions
diff --git a/drivers/misc/issei/main.c b/drivers/misc/issei/main.c
new file mode 100644
index 000000000..5987fb340
--- /dev/null
+++ b/drivers/misc/issei/main.c
@@ -0,0 +1,296 @@
+// 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);