xref: /linux/drivers/misc/issei/main.c (revision 7bd4b9991db20b0df5a8dfbef05920eabd40de28)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (C) 2023-2026 Intel Corporation */
3 #include <linux/atomic.h>
4 #include <linux/device.h>
5 #include <linux/dev_printk.h>
6 #include <linux/err.h>
7 #include <linux/export.h>
8 #include <linux/jiffies.h>
9 #include <linux/kthread.h>
10 #include <linux/slab.h>
11 #include <linux/wait.h>
12 
13 #include "cdev.h"
14 #include "fw_client.h"
15 #include "host_client.h"
16 #include "ham.h"
17 #include "issei_dev.h"
18 
19 static void issei_rst_state_set(struct issei_device *idev, enum issei_rst_state state)
20 {
21 	idev->rst_state = state;
22 	/* wake up the thread */
23 	if (waitqueue_active(&idev->wait_rst_state))
24 		wake_up(&idev->wait_rst_state);
25 }
26 
27 static int issei_reset(struct issei_device *idev)
28 {
29 	int ret;
30 
31 	idev->ops->irq_clear(idev);
32 
33 	issei_cl_all_disconnect(idev);
34 	issei_fw_cl_remove_all(idev);
35 	/* No need to check for overflow here, the counter is used only for info */
36 	idev->all_reset_count++;
37 	ret = idev->ops->hw_reset(idev, !idev->power_down);
38 	issei_dmam_setup(idev);
39 	if (ret) {
40 		dev_err(&idev->dev, "hw_reset failed ret = %d\n", ret);
41 		return ret;
42 	}
43 
44 	if (idev->power_down) {
45 		dev_dbg(&idev->dev, "powering down: end of reset\n");
46 		issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
47 		return -ENODEV;
48 	}
49 	return 0;
50 }
51 
52 static int issei_process_read_msg(struct issei_device *idev)
53 {
54 	struct issei_dma_data data = {};
55 	int ret;
56 
57 	ret = issei_dma_read(idev, &data);
58 	if (ret)
59 		return ret;
60 
61 	dev_dbg(&idev->dev, "Processing response %u %u %u %u\n", data.fw_id, data.host_id,
62 		data.status, data.length);
63 	if (data.status != HAMS_SUCCESS) {
64 		dev_err(&idev->dev, "Command failed with status 0x%02X", data.status);
65 		kfree(data.buf);
66 		ret = -EIO;
67 	} else {
68 		if (issei_is_ham_rsp(data.fw_id, data.host_id))
69 			ret = issei_ham_process_ham_rsp(idev, data.buf, data.length);
70 		else
71 			ret = issei_cl_read_buf(idev, data.fw_id, data.host_id,
72 						data.buf, data.length);
73 	}
74 	idev->ops->irq_write_generate(idev);
75 	return ret;
76 }
77 
78 static int issei_process_write_msg(struct issei_device *idev)
79 {
80 	if (idev->rst_state != ISSEI_RST_STATE_DONE)
81 		return 0;
82 
83 	return issei_cl_write_from_queue(idev);
84 }
85 
86 static int issei_process_thread(void *_dev)
87 {
88 	long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
89 	struct issei_device *idev = _dev;
90 	int ret;
91 
92 	while (!kthread_should_stop()) {
93 		dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
94 		if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
95 			if (!idev->power_down)
96 				dev_dbg(&idev->dev, "HW not ready, resetting\n");
97 			idev->rst_state = ISSEI_RST_STATE_INIT;
98 		}
99 		if (idev->power_down)
100 			idev->rst_state = ISSEI_RST_STATE_INIT;
101 		WRITE_ONCE(idev->has_data, false);
102 		dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
103 		timeout = MAX_SCHEDULE_TIMEOUT;
104 		ret = 0;
105 		switch (idev->rst_state) {
106 		case ISSEI_RST_STATE_DISABLED:
107 			if (idev->power_down) {
108 				dev_dbg(&idev->dev, "Interrupt in power down?\n");
109 				break;
110 			}
111 			idev->rst_state = ISSEI_RST_STATE_INIT;
112 			fallthrough;
113 
114 		case ISSEI_RST_STATE_INIT:
115 			idev->ops->irq_clear(idev);
116 			idev->ops->irq_sync(idev);
117 
118 			if (!idev->power_down) {
119 				idev->reset_count++;
120 				if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
121 					dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
122 					issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
123 					break;
124 				}
125 			}
126 
127 			ret = issei_reset(idev);
128 			if (idev->power_down) {
129 				dev_dbg(&idev->dev, "Powering down\n");
130 				return 0;
131 			}
132 			if (ret)
133 				break;
134 
135 			idev->rst_state = ISSEI_RST_STATE_HW_READY;
136 			timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
137 			break;
138 
139 		case ISSEI_RST_STATE_HW_READY:
140 			if (!idev->ops->hw_is_ready(idev)) {
141 				dev_dbg(&idev->dev, "HW is not ready?\n");
142 				timeout = old_timeout;
143 				break;
144 			}
145 
146 			dev_dbg(&idev->dev, "HW is ready\n");
147 			idev->ops->hw_reset_release(idev);
148 			idev->ops->host_set_ready(idev);
149 			ret = idev->ops->setup_message_send(idev);
150 			if (ret)
151 				break;
152 
153 			idev->rst_state = ISSEI_RST_STATE_SETUP;
154 			timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
155 			break;
156 
157 		case ISSEI_RST_STATE_SETUP:
158 			ret = idev->ops->setup_message_recv(idev);
159 			if (ret) {
160 				if (ret == -ENODATA) {
161 					ret = 0;
162 					timeout = old_timeout;
163 				}
164 			} else {
165 				timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
166 				ret = issei_ham_send_start_req(idev);
167 				idev->rst_state = ISSEI_RST_STATE_START;
168 			}
169 			break;
170 
171 		case ISSEI_RST_STATE_START:
172 			ret = issei_process_read_msg(idev);
173 			if (!ret) {
174 				timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
175 				idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
176 			} else if (ret == -ENODATA) {
177 				ret = 0;
178 				timeout = old_timeout;
179 			}
180 			break;
181 
182 		case ISSEI_RST_STATE_CLIENT_ENUM:
183 			ret = issei_process_read_msg(idev);
184 			if (ret) {
185 				if (ret == -ENODATA) {
186 					ret = 0;
187 					timeout = old_timeout;
188 				}
189 			} else {
190 				idev->reset_count = 0;
191 				idev->rst_state = ISSEI_RST_STATE_DONE;
192 				dev_dbg(&idev->dev, "Reset finished successfully\n");
193 			}
194 			break;
195 
196 		case ISSEI_RST_STATE_DONE:
197 			ret = issei_process_read_msg(idev);
198 			if (ret != 0 && ret != -ENODATA)
199 				break;
200 
201 			ret = issei_process_write_msg(idev);
202 			break;
203 		}
204 
205 		if (ret) {
206 			dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
207 			idev->rst_state = ISSEI_RST_STATE_INIT;
208 			continue;
209 		}
210 
211 		/*
212 		 * Every thread that has data to process sets the 'has_data' flag and
213 		 * triggers the wait queue.
214 		 * The processing thread, in each loop iteration, resets 'has_data'
215 		 * and processes all available data.
216 		 *
217 		 * After processing, the thread waits for 'has_data' to be set again.
218 		 *
219 		 * If the wait function times out but 'has_data' becomes 1 before
220 		 * the subsequent atomic read check, this is acceptable from a flow
221 		 * perspective - the thread will continue processing the data.
222 		 *
223 		 * The 'has_data' flag cannot become 0 between the wait function and
224 		 * the atomic read check, since only this thread is allowed to reset it to 0.
225 		 */
226 
227 		old_timeout = wait_event_interruptible_timeout(idev->wait_has_data,
228 							       READ_ONCE(idev->has_data),
229 							       timeout);
230 		if (idev->rst_state == ISSEI_RST_STATE_DISABLED)
231 			continue;
232 
233 		if (!READ_ONCE(idev->has_data)) {
234 			dev_warn(&idev->dev, "Timed out at state %d, resetting\n",
235 				 idev->rst_state);
236 			idev->rst_state = ISSEI_RST_STATE_INIT;
237 		}
238 	}
239 
240 	return 0;
241 }
242 
243 /**
244  * issei_start - configure HW device and start processing thread.
245  * @idev: the device structure
246  *
247  * Return: 0 on success, < 0 on failure
248  */
249 int issei_start(struct issei_device *idev)
250 {
251 	int ret;
252 
253 	idev->power_down = false;
254 
255 	ret = issei_dmam_setup(idev);
256 	if (ret)
257 		return ret;
258 
259 	idev->ops->irq_clear(idev);
260 
261 	ret = idev->ops->hw_config(idev);
262 	if (ret)
263 		return ret;
264 
265 	idev->process_thread = kthread_run(issei_process_thread, idev,
266 					   "kisseiprocess/%s", dev_name(&idev->dev));
267 	if (IS_ERR(idev->process_thread)) {
268 		ret = PTR_ERR(idev->process_thread);
269 		dev_err(&idev->dev, "unable to create process thread. ret = %d\n", ret);
270 		return ret;
271 	}
272 
273 	issei_poke_process_thread(idev);
274 	return 0;
275 }
276 EXPORT_SYMBOL_GPL(issei_start);
277 
278 /**
279  * issei_stop - stop interrupts and processing thread.
280  * @idev: the device structure
281  */
282 void issei_stop(struct issei_device *idev)
283 {
284 	idev->power_down = true;
285 
286 	idev->ops->irq_clear(idev);
287 	idev->ops->irq_sync(idev);
288 
289 	issei_poke_process_thread(idev);
290 
291 	wait_event_timeout(idev->wait_rst_state,
292 			   (idev->rst_state == ISSEI_RST_STATE_DISABLED),
293 			   msecs_to_jiffies(ISSEI_STOP_TIMEOUT_MSEC));
294 	kthread_stop(idev->process_thread);
295 }
296 EXPORT_SYMBOL_GPL(issei_stop);
297