]>
Commit | Line | Data |
---|---|---|
91f01c6d OW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
733ba91c | 4 | * Copyright (c) 2003-2012, Intel Corporation. |
91f01c6d OW |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | */ | |
16 | ||
17 | #include <linux/pci.h> | |
18 | #include <linux/sched.h> | |
19 | #include <linux/wait.h> | |
20 | #include <linux/delay.h> | |
21 | ||
47a73801 TW |
22 | #include <linux/mei.h> |
23 | ||
91f01c6d | 24 | #include "mei_dev.h" |
91f01c6d | 25 | #include "interface.h" |
91f01c6d | 26 | |
b210d750 TW |
27 | const char *mei_dev_state_str(int state) |
28 | { | |
29 | #define MEI_DEV_STATE(state) case MEI_DEV_##state: return #state | |
30 | switch (state) { | |
31 | MEI_DEV_STATE(INITIALIZING); | |
32 | MEI_DEV_STATE(INIT_CLIENTS); | |
33 | MEI_DEV_STATE(ENABLED); | |
34 | MEI_DEV_STATE(RESETING); | |
35 | MEI_DEV_STATE(DISABLED); | |
36 | MEI_DEV_STATE(RECOVERING_FROM_RESET); | |
37 | MEI_DEV_STATE(POWER_DOWN); | |
38 | MEI_DEV_STATE(POWER_UP); | |
39 | default: | |
40 | return "unkown"; | |
41 | } | |
42 | #undef MEI_DEV_STATE | |
43 | } | |
44 | ||
45 | ||
5bd64714 | 46 | |
91f01c6d | 47 | |
91f01c6d OW |
48 | /** |
49 | * init_mei_device - allocates and initializes the mei device structure | |
50 | * | |
51 | * @pdev: The pci device structure | |
52 | * | |
53 | * returns The mei_device_device pointer on success, NULL on failure. | |
54 | */ | |
c95efb74 | 55 | struct mei_device *mei_device_init(struct pci_dev *pdev) |
91f01c6d | 56 | { |
91f01c6d OW |
57 | struct mei_device *dev; |
58 | ||
59 | dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL); | |
60 | if (!dev) | |
61 | return NULL; | |
62 | ||
63 | /* setup our list array */ | |
91f01c6d OW |
64 | INIT_LIST_HEAD(&dev->file_list); |
65 | INIT_LIST_HEAD(&dev->wd_cl.link); | |
66 | INIT_LIST_HEAD(&dev->iamthif_cl.link); | |
67 | mutex_init(&dev->device_lock); | |
68 | init_waitqueue_head(&dev->wait_recvd_msg); | |
69 | init_waitqueue_head(&dev->wait_stop_wd); | |
b210d750 | 70 | dev->dev_state = MEI_DEV_INITIALIZING; |
91f01c6d | 71 | dev->iamthif_state = MEI_IAMTHIF_IDLE; |
0288c7c9 TW |
72 | |
73 | mei_io_list_init(&dev->read_list); | |
74 | mei_io_list_init(&dev->write_list); | |
75 | mei_io_list_init(&dev->write_waiting_list); | |
76 | mei_io_list_init(&dev->ctrl_wr_list); | |
77 | mei_io_list_init(&dev->ctrl_rd_list); | |
e773efc4 TW |
78 | mei_io_list_init(&dev->amthif_cmd_list); |
79 | mei_io_list_init(&dev->amthif_rd_complete_list); | |
91f01c6d OW |
80 | dev->pdev = pdev; |
81 | return dev; | |
82 | } | |
83 | ||
84 | /** | |
85 | * mei_hw_init - initializes host and fw to start work. | |
86 | * | |
87 | * @dev: the device structure | |
88 | * | |
89 | * returns 0 on success, <0 on failure. | |
90 | */ | |
91 | int mei_hw_init(struct mei_device *dev) | |
92 | { | |
93 | int err = 0; | |
94 | int ret; | |
95 | ||
96 | mutex_lock(&dev->device_lock); | |
97 | ||
98 | dev->host_hw_state = mei_hcsr_read(dev); | |
99 | dev->me_hw_state = mei_mecsr_read(dev); | |
100 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n", | |
101 | dev->host_hw_state, dev->me_hw_state); | |
102 | ||
103 | /* acknowledge interrupt and stop interupts */ | |
3a65dd4e | 104 | mei_clear_interrupts(dev); |
91f01c6d | 105 | |
24aadc80 TW |
106 | /* Doesn't change in runtime */ |
107 | dev->hbuf_depth = (dev->host_hw_state & H_CBD) >> 24; | |
108 | ||
eb9af0ac | 109 | dev->recvd_msg = false; |
91f01c6d OW |
110 | dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); |
111 | ||
112 | mei_reset(dev, 1); | |
113 | ||
114 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | |
115 | dev->host_hw_state, dev->me_hw_state); | |
116 | ||
117 | /* wait for ME to turn on ME_RDY */ | |
118 | if (!dev->recvd_msg) { | |
119 | mutex_unlock(&dev->device_lock); | |
120 | err = wait_event_interruptible_timeout(dev->wait_recvd_msg, | |
3870c320 TW |
121 | dev->recvd_msg, |
122 | mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); | |
91f01c6d OW |
123 | mutex_lock(&dev->device_lock); |
124 | } | |
125 | ||
a534bb6e | 126 | if (err <= 0 && !dev->recvd_msg) { |
b210d750 | 127 | dev->dev_state = MEI_DEV_DISABLED; |
91f01c6d OW |
128 | dev_dbg(&dev->pdev->dev, |
129 | "wait_event_interruptible_timeout failed" | |
130 | "on wait for ME to turn on ME_RDY.\n"); | |
131 | ret = -ENODEV; | |
132 | goto out; | |
133 | } | |
134 | ||
135 | if (!(((dev->host_hw_state & H_RDY) == H_RDY) && | |
136 | ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) { | |
b210d750 | 137 | dev->dev_state = MEI_DEV_DISABLED; |
91f01c6d OW |
138 | dev_dbg(&dev->pdev->dev, |
139 | "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | |
140 | dev->host_hw_state, dev->me_hw_state); | |
141 | ||
8eb73c6c | 142 | if (!(dev->host_hw_state & H_RDY)) |
91f01c6d OW |
143 | dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n"); |
144 | ||
8eb73c6c | 145 | if (!(dev->me_hw_state & ME_RDY_HRA)) |
91f01c6d OW |
146 | dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n"); |
147 | ||
d39a4649 | 148 | dev_err(&dev->pdev->dev, "link layer initialization failed.\n"); |
91f01c6d OW |
149 | ret = -ENODEV; |
150 | goto out; | |
151 | } | |
152 | ||
153 | if (dev->version.major_version != HBM_MAJOR_VERSION || | |
154 | dev->version.minor_version != HBM_MINOR_VERSION) { | |
155 | dev_dbg(&dev->pdev->dev, "MEI start failed.\n"); | |
156 | ret = -ENODEV; | |
157 | goto out; | |
158 | } | |
159 | ||
eb9af0ac | 160 | dev->recvd_msg = false; |
91f01c6d OW |
161 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", |
162 | dev->host_hw_state, dev->me_hw_state); | |
163 | dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n"); | |
164 | dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); | |
165 | dev_dbg(&dev->pdev->dev, "MEI start success.\n"); | |
166 | ret = 0; | |
167 | ||
168 | out: | |
169 | mutex_unlock(&dev->device_lock); | |
170 | return ret; | |
171 | } | |
172 | ||
173 | /** | |
174 | * mei_hw_reset - resets fw via mei csr register. | |
175 | * | |
176 | * @dev: the device structure | |
177 | * @interrupts_enabled: if interrupt should be enabled after reset. | |
178 | */ | |
179 | static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled) | |
180 | { | |
181 | dev->host_hw_state |= (H_RST | H_IG); | |
182 | ||
183 | if (interrupts_enabled) | |
184 | mei_enable_interrupts(dev); | |
185 | else | |
186 | mei_disable_interrupts(dev); | |
187 | } | |
188 | ||
189 | /** | |
190 | * mei_reset - resets host and fw. | |
191 | * | |
192 | * @dev: the device structure | |
193 | * @interrupts_enabled: if interrupt should be enabled after reset. | |
194 | */ | |
195 | void mei_reset(struct mei_device *dev, int interrupts_enabled) | |
196 | { | |
197 | struct mei_cl *cl_pos = NULL; | |
198 | struct mei_cl *cl_next = NULL; | |
199 | struct mei_cl_cb *cb_pos = NULL; | |
200 | struct mei_cl_cb *cb_next = NULL; | |
201 | bool unexpected; | |
202 | ||
b210d750 | 203 | if (dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) { |
eb9af0ac | 204 | dev->need_reset = true; |
91f01c6d OW |
205 | return; |
206 | } | |
207 | ||
b210d750 TW |
208 | unexpected = (dev->dev_state != MEI_DEV_INITIALIZING && |
209 | dev->dev_state != MEI_DEV_DISABLED && | |
210 | dev->dev_state != MEI_DEV_POWER_DOWN && | |
211 | dev->dev_state != MEI_DEV_POWER_UP); | |
91f01c6d OW |
212 | |
213 | dev->host_hw_state = mei_hcsr_read(dev); | |
214 | ||
215 | dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n", | |
216 | dev->host_hw_state); | |
217 | ||
218 | mei_hw_reset(dev, interrupts_enabled); | |
219 | ||
220 | dev->host_hw_state &= ~H_RST; | |
221 | dev->host_hw_state |= H_IG; | |
222 | ||
223 | mei_hcsr_set(dev); | |
224 | ||
225 | dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", | |
226 | dev->host_hw_state); | |
227 | ||
eb9af0ac | 228 | dev->need_reset = false; |
91f01c6d | 229 | |
b210d750 TW |
230 | if (dev->dev_state != MEI_DEV_INITIALIZING) { |
231 | if (dev->dev_state != MEI_DEV_DISABLED && | |
232 | dev->dev_state != MEI_DEV_POWER_DOWN) | |
233 | dev->dev_state = MEI_DEV_RESETING; | |
91f01c6d OW |
234 | |
235 | list_for_each_entry_safe(cl_pos, | |
236 | cl_next, &dev->file_list, link) { | |
237 | cl_pos->state = MEI_FILE_DISCONNECTED; | |
238 | cl_pos->mei_flow_ctrl_creds = 0; | |
239 | cl_pos->read_cb = NULL; | |
240 | cl_pos->timer_count = 0; | |
241 | } | |
242 | /* remove entry if already in list */ | |
ff8b2f4e TW |
243 | dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); |
244 | mei_me_cl_unlink(dev, &dev->wd_cl); | |
91f01c6d | 245 | |
ff8b2f4e | 246 | mei_me_cl_unlink(dev, &dev->iamthif_cl); |
91f01c6d | 247 | |
19838fb8 | 248 | mei_amthif_reset_params(dev); |
5fb54fb4 | 249 | memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); |
91f01c6d OW |
250 | } |
251 | ||
cf9673da | 252 | dev->me_clients_num = 0; |
91f01c6d | 253 | dev->rd_msg_hdr = 0; |
eb9af0ac | 254 | dev->wd_pending = false; |
91f01c6d OW |
255 | |
256 | /* update the state of the registers after reset */ | |
257 | dev->host_hw_state = mei_hcsr_read(dev); | |
258 | dev->me_hw_state = mei_mecsr_read(dev); | |
259 | ||
260 | dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | |
261 | dev->host_hw_state, dev->me_hw_state); | |
262 | ||
263 | if (unexpected) | |
b210d750 TW |
264 | dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", |
265 | mei_dev_state_str(dev->dev_state)); | |
91f01c6d OW |
266 | |
267 | /* Wake up all readings so they can be interrupted */ | |
268 | list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { | |
269 | if (waitqueue_active(&cl_pos->rx_wait)) { | |
270 | dev_dbg(&dev->pdev->dev, "Waking up client!\n"); | |
271 | wake_up_interruptible(&cl_pos->rx_wait); | |
272 | } | |
273 | } | |
274 | /* remove all waiting requests */ | |
fb601adb TW |
275 | list_for_each_entry_safe(cb_pos, cb_next, &dev->write_list.list, list) { |
276 | list_del(&cb_pos->list); | |
601a1efa | 277 | mei_io_cb_free(cb_pos); |
91f01c6d OW |
278 | } |
279 | } | |
280 | ||
281 | ||
91f01c6d OW |
282 | /** |
283 | * allocate_me_clients_storage - allocates storage for me clients | |
284 | * | |
285 | * @dev: the device structure | |
286 | * | |
287 | * returns none. | |
288 | */ | |
c95efb74 | 289 | void mei_allocate_me_clients_storage(struct mei_device *dev) |
91f01c6d OW |
290 | { |
291 | struct mei_me_client *clients; | |
292 | int b; | |
293 | ||
294 | /* count how many ME clients we have */ | |
295 | for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) | |
cf9673da | 296 | dev->me_clients_num++; |
91f01c6d | 297 | |
cf9673da | 298 | if (dev->me_clients_num <= 0) |
91f01c6d OW |
299 | return ; |
300 | ||
301 | ||
302 | if (dev->me_clients != NULL) { | |
303 | kfree(dev->me_clients); | |
304 | dev->me_clients = NULL; | |
305 | } | |
306 | dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", | |
cf9673da | 307 | dev->me_clients_num * sizeof(struct mei_me_client)); |
91f01c6d | 308 | /* allocate storage for ME clients representation */ |
cf9673da | 309 | clients = kcalloc(dev->me_clients_num, |
91f01c6d OW |
310 | sizeof(struct mei_me_client), GFP_KERNEL); |
311 | if (!clients) { | |
312 | dev_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); | |
b210d750 | 313 | dev->dev_state = MEI_DEV_RESETING; |
91f01c6d OW |
314 | mei_reset(dev, 1); |
315 | return ; | |
316 | } | |
317 | dev->me_clients = clients; | |
318 | return ; | |
319 | } | |
c1174c0e | 320 | |
c1174c0e | 321 | |
91f01c6d | 322 |