]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blob - drivers/usb/intel_ulpss/usb_stub.c
UBUNTU: SAUCE: IPU driver release WW48 with MCU
[mirror_ubuntu-jammy-kernel.git] / drivers / usb / intel_ulpss / usb_stub.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Intel LPSS USB driver
4 *
5 * Copyright (c) 2020, Intel Corporation.
6 */
7
8 #include <linux/acpi.h>
9 #include <linux/errno.h>
10 #include <linux/slab.h>
11
12 #include "protocol_intel_ulpss.h"
13 #include "ulpss_bridge.h"
14
15 #define MAKE_CMD_ID(type, cmd) ((type << 8) | cmd)
16 static bool usb_stub_validate(u8 *data, u32 data_len)
17 {
18 bool is_valid = true;
19 struct cmd_header *header = (struct cmd_header *)data;
20
21 /* verify the respone flag */
22 if (header->cmd != GPIO_INTR_NOTIFY &&
23 ((header->flags & RESP_FLAG) == 0))
24 is_valid = false;
25
26 /* verify the payload len */
27 is_valid = is_valid && (header->len + sizeof(*header) == data_len);
28
29 return is_valid;
30 }
31
32 static int usb_stub_parse(struct usb_stub *stub, struct cmd_header *header)
33 {
34 int ret = 0;
35
36 if (!stub || !header || (header->len < 0))
37 return -EINVAL;
38
39 stub->len = header->len;
40
41 if (header->len == 0)
42 return 0;
43
44 memcpy(stub->buf, header->data, header->len);
45 if (stub->parse)
46 ret = stub->parse(stub, header->cmd, header->flags,
47 header->data, header->len);
48
49 return ret;
50 }
51
52 /*
53 * Bottom half processing work function (instead of thread handler)
54 * for processing fw messages
55 */
56 static void event_work_cb(struct work_struct *work)
57 {
58 struct usb_bridge *intel_ulpss_dev;
59 struct bridge_msg msg_in_proc = { 0 };
60 struct usb_stub *stub;
61 struct cmd_header *header;
62 int rcv_cmd_id;
63 int ret;
64
65 intel_ulpss_dev = container_of(work, struct usb_bridge, event_work);
66 BUG_ON(!intel_ulpss_dev);
67
68 while (kfifo_get(&intel_ulpss_dev->msg_fifo, &msg_in_proc)) {
69 if (!msg_in_proc.len)
70 continue;
71
72 header = (struct cmd_header *)msg_in_proc.buf;
73
74 dev_dbg(&intel_ulpss_dev->intf->dev,
75 "receive: type:%d cmd:%d flags:%d len:%d\n",
76 header->type, header->cmd, header->flags, header->len);
77
78 /* verify the data */
79 if (!usb_stub_validate(msg_in_proc.buf, msg_in_proc.len)) {
80 dev_err(&intel_ulpss_dev->intf->dev,
81 "%s header->len:%d payload_len:%ld\n ",
82 __func__, header->len, msg_in_proc.len);
83 continue;
84 }
85
86 stub = usb_stub_find(intel_ulpss_dev->intf, header->type);
87 ret = usb_stub_parse(stub, header);
88 if (ret) {
89 dev_err(&intel_ulpss_dev->intf->dev,
90 "%s failed to parse data: ret:%d type:%d len: %d",
91 __func__, ret, header->type, header->len);
92 continue;
93 }
94
95 rcv_cmd_id = MAKE_CMD_ID(stub->type, header->cmd);
96 if (rcv_cmd_id == stub->cmd_id) {
97 stub->acked = true;
98 wake_up_interruptible(&intel_ulpss_dev->bulk_out_ack);
99
100 } else {
101 dev_warn(&intel_ulpss_dev->intf->dev,
102 "%s rcv_cmd_id:%x != stub->cmd_id:%x",
103 __func__, rcv_cmd_id, stub->cmd_id);
104 }
105 }
106 }
107
108 struct usb_stub *usb_stub_alloc(struct usb_interface *intf)
109 {
110 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
111 struct usb_stub *cur_stub;
112
113 cur_stub = kzalloc(sizeof(struct usb_stub), GFP_KERNEL);
114 if (!cur_stub) {
115 dev_err(&intf->dev, "%s no memory for new stub", __func__);
116 return NULL;
117 }
118
119 mutex_init(&cur_stub->stub_mutex);
120 INIT_LIST_HEAD(&cur_stub->list);
121
122 list_add_tail(&cur_stub->list, &intel_ulpss_dev->stubs_list);
123 intel_ulpss_dev->stub_count++;
124 dev_dbg(&intf->dev,
125 "%s enuming stub intel_ulpss_dev->stub_count:%ld type:%d success\n",
126 __func__, intel_ulpss_dev->stub_count, cur_stub->type);
127
128 return cur_stub;
129 }
130
131 int usb_stub_init(struct usb_interface *intf)
132 {
133 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
134
135 if (!intel_ulpss_dev)
136 return -EINVAL;
137
138 INIT_WORK(&intel_ulpss_dev->event_work, event_work_cb);
139
140 return 0;
141 }
142
143 struct usb_stub *usb_stub_find(struct usb_interface *intf, u8 type)
144 {
145 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
146 struct usb_stub *cur_stub;
147
148 if (!intel_ulpss_dev)
149 return NULL;
150
151 list_for_each_entry (cur_stub, &intel_ulpss_dev->stubs_list, list) {
152 if (cur_stub->type == type)
153 return cur_stub;
154 }
155
156 dev_err(&intf->dev, "%s usb stub not find, type: %d", __func__, type);
157 return NULL;
158 }
159
160 int usb_stub_write(struct usb_stub *stub, u8 cmd, u8 *data, u8 len,
161 bool wait_ack, long timeout)
162 {
163 int ret;
164 u8 flags = 0;
165 u8 buff[MAX_PACKET_SIZE] = { 0 };
166
167 struct cmd_header *header;
168 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(stub->intf);
169
170 if (!stub || !intel_ulpss_dev)
171 return -EINVAL;
172
173 header = (struct cmd_header *)buff;
174 if (len >= MAX_PAYLOAD_SIZE)
175 return -ENOMEM;
176
177 if (wait_ack)
178 flags |= ACK_FLAG;
179
180 flags |= CMPL_FLAG;
181
182 header->type = stub->type;
183 header->cmd = cmd;
184 header->flags = flags;
185 header->len = len;
186
187 memcpy(header->data, data, len);
188 dev_dbg(&intel_ulpss_dev->intf->dev,
189 "send: type:%d cmd:%d flags:%d len:%d\n", header->type,
190 header->cmd, header->flags, header->len);
191
192 stub->cmd_id = MAKE_CMD_ID(stub->type, cmd);
193
194 ret = intel_ulpss_bridge_write(
195 stub->intf, header, sizeof(struct cmd_header) + len, timeout);
196
197 if (ret != sizeof(struct cmd_header) + len) {
198 dev_err(&intel_ulpss_dev->intf->dev,
199 "%s bridge write failed ret:%d total_len:%ld\n ",
200 __func__, ret, sizeof(struct cmd_header) + len);
201 return -EIO;
202 }
203
204 if (flags & ACK_FLAG) {
205 ret = wait_event_interruptible_timeout(
206 intel_ulpss_dev->bulk_out_ack, (stub->acked), timeout);
207 stub->acked = false;
208
209 if (ret < 0) {
210 dev_err(&intel_ulpss_dev->intf->dev,
211 "acked wait interrupted ret:%d timeout:%ld ack:%d\n",
212 ret, timeout, stub->acked);
213 return ret;
214
215 } else if (ret == 0) {
216 dev_err(&intel_ulpss_dev->intf->dev,
217 "acked sem wait timed out ret:%d timeout:%ld ack:%d\n",
218 ret, timeout, stub->acked);
219 return -ETIMEDOUT;
220 }
221 }
222
223 return 0;
224 }
225
226 void usb_stub_broadcast(struct usb_interface *intf, long event,
227 void *event_data)
228 {
229 int ret = 0;
230 struct usb_stub *cur_stub = NULL;
231 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
232
233 if (!intel_ulpss_dev)
234 return;
235
236 list_for_each_entry (cur_stub, &intel_ulpss_dev->stubs_list, list) {
237 if (cur_stub && cur_stub->notify) {
238 ret = cur_stub->notify(cur_stub, event, event_data);
239 if (ret)
240 continue;
241 }
242 }
243 }
244
245 void usb_stub_cleanup(struct usb_interface *intf)
246 {
247 struct usb_stub *cur_stub = NULL;
248 struct usb_stub *next = NULL;
249 struct usb_bridge *intel_ulpss_dev = usb_get_intfdata(intf);
250
251 if (!intel_ulpss_dev)
252 return;
253
254 list_for_each_entry_safe (cur_stub, next, &intel_ulpss_dev->stubs_list,
255 list) {
256 if (!cur_stub)
257 continue;
258
259 list_del_init(&cur_stub->list);
260 dev_dbg(&intf->dev, "%s type:%d\n ", __func__, cur_stub->type);
261 if (cur_stub->cleanup)
262 cur_stub->cleanup(cur_stub);
263
264 mutex_destroy(&cur_stub->stub_mutex);
265 kfree(cur_stub);
266
267 intel_ulpss_dev->stub_count--;
268 }
269 }
270
271 struct acpi_device *find_adev_by_hid(struct acpi_device *parent,
272 const char *hid)
273 {
274 struct acpi_device *adev;
275
276 if (!parent)
277 return NULL;
278
279 list_for_each_entry (adev, &parent->children, node) {
280 if (!strcmp(hid, acpi_device_hid(adev)))
281 return adev;
282 }
283
284 return NULL;
285 }