1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2015-2018, Intel Corporation. |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) "kcs-bmc: " fmt |
7 | |
8 | #include <linux/errno.h> |
9 | #include <linux/io.h> |
10 | #include <linux/ipmi_bmc.h> |
11 | #include <linux/list.h> |
12 | #include <linux/miscdevice.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mutex.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/poll.h> |
17 | #include <linux/sched.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #include "kcs_bmc_client.h" |
21 | |
22 | /* Different phases of the KCS BMC module. |
23 | * KCS_PHASE_IDLE: |
24 | * BMC should not be expecting nor sending any data. |
25 | * KCS_PHASE_WRITE_START: |
26 | * BMC is receiving a WRITE_START command from system software. |
27 | * KCS_PHASE_WRITE_DATA: |
28 | * BMC is receiving a data byte from system software. |
29 | * KCS_PHASE_WRITE_END_CMD: |
30 | * BMC is waiting a last data byte from system software. |
31 | * KCS_PHASE_WRITE_DONE: |
32 | * BMC has received the whole request from system software. |
33 | * KCS_PHASE_WAIT_READ: |
34 | * BMC is waiting the response from the upper IPMI service. |
35 | * KCS_PHASE_READ: |
36 | * BMC is transferring the response to system software. |
37 | * KCS_PHASE_ABORT_ERROR1: |
38 | * BMC is waiting error status request from system software. |
39 | * KCS_PHASE_ABORT_ERROR2: |
40 | * BMC is waiting for idle status afer error from system software. |
41 | * KCS_PHASE_ERROR: |
42 | * BMC has detected a protocol violation at the interface level. |
43 | */ |
44 | enum kcs_ipmi_phases { |
45 | KCS_PHASE_IDLE, |
46 | |
47 | KCS_PHASE_WRITE_START, |
48 | KCS_PHASE_WRITE_DATA, |
49 | KCS_PHASE_WRITE_END_CMD, |
50 | KCS_PHASE_WRITE_DONE, |
51 | |
52 | KCS_PHASE_WAIT_READ, |
53 | KCS_PHASE_READ, |
54 | |
55 | KCS_PHASE_ABORT_ERROR1, |
56 | KCS_PHASE_ABORT_ERROR2, |
57 | KCS_PHASE_ERROR |
58 | }; |
59 | |
60 | /* IPMI 2.0 - Table 9-4, KCS Interface Status Codes */ |
61 | enum kcs_ipmi_errors { |
62 | KCS_NO_ERROR = 0x00, |
63 | KCS_ABORTED_BY_COMMAND = 0x01, |
64 | KCS_ILLEGAL_CONTROL_CODE = 0x02, |
65 | KCS_LENGTH_ERROR = 0x06, |
66 | KCS_UNSPECIFIED_ERROR = 0xFF |
67 | }; |
68 | |
69 | struct kcs_bmc_ipmi { |
70 | struct list_head entry; |
71 | |
72 | struct kcs_bmc_client client; |
73 | |
74 | spinlock_t lock; |
75 | |
76 | enum kcs_ipmi_phases phase; |
77 | enum kcs_ipmi_errors error; |
78 | |
79 | wait_queue_head_t queue; |
80 | bool data_in_avail; |
81 | int data_in_idx; |
82 | u8 *data_in; |
83 | |
84 | int data_out_idx; |
85 | int data_out_len; |
86 | u8 *data_out; |
87 | |
88 | struct mutex mutex; |
89 | u8 *kbuffer; |
90 | |
91 | struct miscdevice miscdev; |
92 | }; |
93 | |
94 | #define DEVICE_NAME "ipmi-kcs" |
95 | |
96 | #define KCS_MSG_BUFSIZ 1000 |
97 | |
98 | #define KCS_ZERO_DATA 0 |
99 | |
100 | /* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */ |
101 | #define KCS_STATUS_STATE(state) (state << 6) |
102 | #define KCS_STATUS_STATE_MASK GENMASK(7, 6) |
103 | #define KCS_STATUS_CMD_DAT BIT(3) |
104 | #define KCS_STATUS_SMS_ATN BIT(2) |
105 | #define KCS_STATUS_IBF BIT(1) |
106 | #define KCS_STATUS_OBF BIT(0) |
107 | |
108 | /* IPMI 2.0 - Table 9-2, KCS Interface State Bits */ |
109 | enum kcs_states { |
110 | IDLE_STATE = 0, |
111 | READ_STATE = 1, |
112 | WRITE_STATE = 2, |
113 | ERROR_STATE = 3, |
114 | }; |
115 | |
116 | /* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */ |
117 | #define KCS_CMD_GET_STATUS_ABORT 0x60 |
118 | #define KCS_CMD_WRITE_START 0x61 |
119 | #define KCS_CMD_WRITE_END 0x62 |
120 | #define KCS_CMD_READ_BYTE 0x68 |
121 | |
122 | static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state) |
123 | { |
124 | kcs_bmc_update_status(kcs_bmc: priv->client.dev, KCS_STATUS_STATE_MASK, KCS_STATUS_STATE(state)); |
125 | } |
126 | |
127 | static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv) |
128 | { |
129 | set_state(priv, state: ERROR_STATE); |
130 | kcs_bmc_read_data(kcs_bmc: priv->client.dev); |
131 | kcs_bmc_write_data(kcs_bmc: priv->client.dev, KCS_ZERO_DATA); |
132 | |
133 | priv->phase = KCS_PHASE_ERROR; |
134 | priv->data_in_avail = false; |
135 | priv->data_in_idx = 0; |
136 | } |
137 | |
138 | static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv) |
139 | { |
140 | struct kcs_bmc_device *dev; |
141 | u8 data; |
142 | |
143 | dev = priv->client.dev; |
144 | |
145 | switch (priv->phase) { |
146 | case KCS_PHASE_WRITE_START: |
147 | priv->phase = KCS_PHASE_WRITE_DATA; |
148 | fallthrough; |
149 | |
150 | case KCS_PHASE_WRITE_DATA: |
151 | if (priv->data_in_idx < KCS_MSG_BUFSIZ) { |
152 | set_state(priv, state: WRITE_STATE); |
153 | kcs_bmc_write_data(kcs_bmc: dev, KCS_ZERO_DATA); |
154 | priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(kcs_bmc: dev); |
155 | } else { |
156 | kcs_bmc_ipmi_force_abort(priv); |
157 | priv->error = KCS_LENGTH_ERROR; |
158 | } |
159 | break; |
160 | |
161 | case KCS_PHASE_WRITE_END_CMD: |
162 | if (priv->data_in_idx < KCS_MSG_BUFSIZ) { |
163 | set_state(priv, state: READ_STATE); |
164 | priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(kcs_bmc: dev); |
165 | priv->phase = KCS_PHASE_WRITE_DONE; |
166 | priv->data_in_avail = true; |
167 | wake_up_interruptible(&priv->queue); |
168 | } else { |
169 | kcs_bmc_ipmi_force_abort(priv); |
170 | priv->error = KCS_LENGTH_ERROR; |
171 | } |
172 | break; |
173 | |
174 | case KCS_PHASE_READ: |
175 | if (priv->data_out_idx == priv->data_out_len) |
176 | set_state(priv, state: IDLE_STATE); |
177 | |
178 | data = kcs_bmc_read_data(kcs_bmc: dev); |
179 | if (data != KCS_CMD_READ_BYTE) { |
180 | set_state(priv, state: ERROR_STATE); |
181 | kcs_bmc_write_data(kcs_bmc: dev, KCS_ZERO_DATA); |
182 | break; |
183 | } |
184 | |
185 | if (priv->data_out_idx == priv->data_out_len) { |
186 | kcs_bmc_write_data(kcs_bmc: dev, KCS_ZERO_DATA); |
187 | priv->phase = KCS_PHASE_IDLE; |
188 | break; |
189 | } |
190 | |
191 | kcs_bmc_write_data(kcs_bmc: dev, data: priv->data_out[priv->data_out_idx++]); |
192 | break; |
193 | |
194 | case KCS_PHASE_ABORT_ERROR1: |
195 | set_state(priv, state: READ_STATE); |
196 | kcs_bmc_read_data(kcs_bmc: dev); |
197 | kcs_bmc_write_data(kcs_bmc: dev, data: priv->error); |
198 | priv->phase = KCS_PHASE_ABORT_ERROR2; |
199 | break; |
200 | |
201 | case KCS_PHASE_ABORT_ERROR2: |
202 | set_state(priv, state: IDLE_STATE); |
203 | kcs_bmc_read_data(kcs_bmc: dev); |
204 | kcs_bmc_write_data(kcs_bmc: dev, KCS_ZERO_DATA); |
205 | priv->phase = KCS_PHASE_IDLE; |
206 | break; |
207 | |
208 | default: |
209 | kcs_bmc_ipmi_force_abort(priv); |
210 | break; |
211 | } |
212 | } |
213 | |
214 | static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv) |
215 | { |
216 | u8 cmd; |
217 | |
218 | set_state(priv, state: WRITE_STATE); |
219 | kcs_bmc_write_data(kcs_bmc: priv->client.dev, KCS_ZERO_DATA); |
220 | |
221 | cmd = kcs_bmc_read_data(kcs_bmc: priv->client.dev); |
222 | switch (cmd) { |
223 | case KCS_CMD_WRITE_START: |
224 | priv->phase = KCS_PHASE_WRITE_START; |
225 | priv->error = KCS_NO_ERROR; |
226 | priv->data_in_avail = false; |
227 | priv->data_in_idx = 0; |
228 | break; |
229 | |
230 | case KCS_CMD_WRITE_END: |
231 | if (priv->phase != KCS_PHASE_WRITE_DATA) { |
232 | kcs_bmc_ipmi_force_abort(priv); |
233 | break; |
234 | } |
235 | |
236 | priv->phase = KCS_PHASE_WRITE_END_CMD; |
237 | break; |
238 | |
239 | case KCS_CMD_GET_STATUS_ABORT: |
240 | if (priv->error == KCS_NO_ERROR) |
241 | priv->error = KCS_ABORTED_BY_COMMAND; |
242 | |
243 | priv->phase = KCS_PHASE_ABORT_ERROR1; |
244 | priv->data_in_avail = false; |
245 | priv->data_in_idx = 0; |
246 | break; |
247 | |
248 | default: |
249 | kcs_bmc_ipmi_force_abort(priv); |
250 | priv->error = KCS_ILLEGAL_CONTROL_CODE; |
251 | break; |
252 | } |
253 | } |
254 | |
255 | static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client) |
256 | { |
257 | return container_of(client, struct kcs_bmc_ipmi, client); |
258 | } |
259 | |
260 | static irqreturn_t kcs_bmc_ipmi_event(struct kcs_bmc_client *client) |
261 | { |
262 | struct kcs_bmc_ipmi *priv; |
263 | u8 status; |
264 | int ret; |
265 | |
266 | priv = client_to_kcs_bmc_ipmi(client); |
267 | if (!priv) |
268 | return IRQ_NONE; |
269 | |
270 | spin_lock(lock: &priv->lock); |
271 | |
272 | status = kcs_bmc_read_status(kcs_bmc: client->dev); |
273 | if (status & KCS_STATUS_IBF) { |
274 | if (status & KCS_STATUS_CMD_DAT) |
275 | kcs_bmc_ipmi_handle_cmd(priv); |
276 | else |
277 | kcs_bmc_ipmi_handle_data(priv); |
278 | |
279 | ret = IRQ_HANDLED; |
280 | } else { |
281 | ret = IRQ_NONE; |
282 | } |
283 | |
284 | spin_unlock(lock: &priv->lock); |
285 | |
286 | return ret; |
287 | } |
288 | |
289 | static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = { |
290 | .event = kcs_bmc_ipmi_event, |
291 | }; |
292 | |
293 | static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp) |
294 | { |
295 | return container_of(filp->private_data, struct kcs_bmc_ipmi, miscdev); |
296 | } |
297 | |
298 | static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp) |
299 | { |
300 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
301 | |
302 | return kcs_bmc_enable_device(kcs_bmc: priv->client.dev, client: &priv->client); |
303 | } |
304 | |
305 | static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait) |
306 | { |
307 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
308 | __poll_t mask = 0; |
309 | |
310 | poll_wait(filp, wait_address: &priv->queue, p: wait); |
311 | |
312 | spin_lock_irq(lock: &priv->lock); |
313 | if (priv->data_in_avail) |
314 | mask |= EPOLLIN; |
315 | spin_unlock_irq(lock: &priv->lock); |
316 | |
317 | return mask; |
318 | } |
319 | |
320 | static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf, |
321 | size_t count, loff_t *ppos) |
322 | { |
323 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
324 | bool data_avail; |
325 | size_t data_len; |
326 | ssize_t ret; |
327 | |
328 | if (!(filp->f_flags & O_NONBLOCK)) |
329 | wait_event_interruptible(priv->queue, |
330 | priv->data_in_avail); |
331 | |
332 | mutex_lock(&priv->mutex); |
333 | |
334 | spin_lock_irq(lock: &priv->lock); |
335 | data_avail = priv->data_in_avail; |
336 | if (data_avail) { |
337 | data_len = priv->data_in_idx; |
338 | memcpy(priv->kbuffer, priv->data_in, data_len); |
339 | } |
340 | spin_unlock_irq(lock: &priv->lock); |
341 | |
342 | if (!data_avail) { |
343 | ret = -EAGAIN; |
344 | goto out_unlock; |
345 | } |
346 | |
347 | if (count < data_len) { |
348 | pr_err("channel=%u with too large data : %zu\n" , |
349 | priv->client.dev->channel, data_len); |
350 | |
351 | spin_lock_irq(lock: &priv->lock); |
352 | kcs_bmc_ipmi_force_abort(priv); |
353 | spin_unlock_irq(lock: &priv->lock); |
354 | |
355 | ret = -EOVERFLOW; |
356 | goto out_unlock; |
357 | } |
358 | |
359 | if (copy_to_user(to: buf, from: priv->kbuffer, n: data_len)) { |
360 | ret = -EFAULT; |
361 | goto out_unlock; |
362 | } |
363 | |
364 | ret = data_len; |
365 | |
366 | spin_lock_irq(lock: &priv->lock); |
367 | if (priv->phase == KCS_PHASE_WRITE_DONE) { |
368 | priv->phase = KCS_PHASE_WAIT_READ; |
369 | priv->data_in_avail = false; |
370 | priv->data_in_idx = 0; |
371 | } else { |
372 | ret = -EAGAIN; |
373 | } |
374 | spin_unlock_irq(lock: &priv->lock); |
375 | |
376 | out_unlock: |
377 | mutex_unlock(lock: &priv->mutex); |
378 | |
379 | return ret; |
380 | } |
381 | |
382 | static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf, |
383 | size_t count, loff_t *ppos) |
384 | { |
385 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
386 | ssize_t ret; |
387 | |
388 | /* a minimum response size '3' : netfn + cmd + ccode */ |
389 | if (count < 3 || count > KCS_MSG_BUFSIZ) |
390 | return -EINVAL; |
391 | |
392 | mutex_lock(&priv->mutex); |
393 | |
394 | if (copy_from_user(to: priv->kbuffer, from: buf, n: count)) { |
395 | ret = -EFAULT; |
396 | goto out_unlock; |
397 | } |
398 | |
399 | spin_lock_irq(lock: &priv->lock); |
400 | if (priv->phase == KCS_PHASE_WAIT_READ) { |
401 | priv->phase = KCS_PHASE_READ; |
402 | priv->data_out_idx = 1; |
403 | priv->data_out_len = count; |
404 | memcpy(priv->data_out, priv->kbuffer, count); |
405 | kcs_bmc_write_data(kcs_bmc: priv->client.dev, data: priv->data_out[0]); |
406 | ret = count; |
407 | } else { |
408 | ret = -EINVAL; |
409 | } |
410 | spin_unlock_irq(lock: &priv->lock); |
411 | |
412 | out_unlock: |
413 | mutex_unlock(lock: &priv->mutex); |
414 | |
415 | return ret; |
416 | } |
417 | |
418 | static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd, |
419 | unsigned long arg) |
420 | { |
421 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
422 | long ret = 0; |
423 | |
424 | spin_lock_irq(lock: &priv->lock); |
425 | |
426 | switch (cmd) { |
427 | case IPMI_BMC_IOCTL_SET_SMS_ATN: |
428 | kcs_bmc_update_status(kcs_bmc: priv->client.dev, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN); |
429 | break; |
430 | |
431 | case IPMI_BMC_IOCTL_CLEAR_SMS_ATN: |
432 | kcs_bmc_update_status(kcs_bmc: priv->client.dev, KCS_STATUS_SMS_ATN, val: 0); |
433 | break; |
434 | |
435 | case IPMI_BMC_IOCTL_FORCE_ABORT: |
436 | kcs_bmc_ipmi_force_abort(priv); |
437 | break; |
438 | |
439 | default: |
440 | ret = -EINVAL; |
441 | break; |
442 | } |
443 | |
444 | spin_unlock_irq(lock: &priv->lock); |
445 | |
446 | return ret; |
447 | } |
448 | |
449 | static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp) |
450 | { |
451 | struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp); |
452 | |
453 | kcs_bmc_ipmi_force_abort(priv); |
454 | kcs_bmc_disable_device(kcs_bmc: priv->client.dev, client: &priv->client); |
455 | |
456 | return 0; |
457 | } |
458 | |
459 | static const struct file_operations kcs_bmc_ipmi_fops = { |
460 | .owner = THIS_MODULE, |
461 | .open = kcs_bmc_ipmi_open, |
462 | .read = kcs_bmc_ipmi_read, |
463 | .write = kcs_bmc_ipmi_write, |
464 | .release = kcs_bmc_ipmi_release, |
465 | .poll = kcs_bmc_ipmi_poll, |
466 | .unlocked_ioctl = kcs_bmc_ipmi_ioctl, |
467 | }; |
468 | |
469 | static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock); |
470 | static LIST_HEAD(kcs_bmc_ipmi_instances); |
471 | |
472 | static int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc) |
473 | { |
474 | struct kcs_bmc_ipmi *priv; |
475 | int rc; |
476 | |
477 | priv = devm_kzalloc(dev: kcs_bmc->dev, size: sizeof(*priv), GFP_KERNEL); |
478 | if (!priv) |
479 | return -ENOMEM; |
480 | |
481 | spin_lock_init(&priv->lock); |
482 | mutex_init(&priv->mutex); |
483 | |
484 | init_waitqueue_head(&priv->queue); |
485 | |
486 | priv->client.dev = kcs_bmc; |
487 | priv->client.ops = &kcs_bmc_ipmi_client_ops; |
488 | priv->data_in = devm_kmalloc(dev: kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL); |
489 | priv->data_out = devm_kmalloc(dev: kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL); |
490 | priv->kbuffer = devm_kmalloc(dev: kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL); |
491 | |
492 | priv->miscdev.minor = MISC_DYNAMIC_MINOR; |
493 | priv->miscdev.name = devm_kasprintf(dev: kcs_bmc->dev, GFP_KERNEL, fmt: "%s%u" , DEVICE_NAME, |
494 | kcs_bmc->channel); |
495 | if (!priv->data_in || !priv->data_out || !priv->kbuffer || !priv->miscdev.name) |
496 | return -EINVAL; |
497 | |
498 | priv->miscdev.fops = &kcs_bmc_ipmi_fops; |
499 | |
500 | rc = misc_register(misc: &priv->miscdev); |
501 | if (rc) { |
502 | dev_err(kcs_bmc->dev, "Unable to register device: %d\n" , rc); |
503 | return rc; |
504 | } |
505 | |
506 | spin_lock_irq(lock: &kcs_bmc_ipmi_instances_lock); |
507 | list_add(new: &priv->entry, head: &kcs_bmc_ipmi_instances); |
508 | spin_unlock_irq(lock: &kcs_bmc_ipmi_instances_lock); |
509 | |
510 | dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d" , kcs_bmc->channel); |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc) |
516 | { |
517 | struct kcs_bmc_ipmi *priv = NULL, *pos; |
518 | |
519 | spin_lock_irq(lock: &kcs_bmc_ipmi_instances_lock); |
520 | list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) { |
521 | if (pos->client.dev == kcs_bmc) { |
522 | priv = pos; |
523 | list_del(entry: &pos->entry); |
524 | break; |
525 | } |
526 | } |
527 | spin_unlock_irq(lock: &kcs_bmc_ipmi_instances_lock); |
528 | |
529 | if (!priv) |
530 | return -ENODEV; |
531 | |
532 | misc_deregister(misc: &priv->miscdev); |
533 | kcs_bmc_disable_device(kcs_bmc: priv->client.dev, client: &priv->client); |
534 | devm_kfree(dev: kcs_bmc->dev, p: priv->kbuffer); |
535 | devm_kfree(dev: kcs_bmc->dev, p: priv->data_out); |
536 | devm_kfree(dev: kcs_bmc->dev, p: priv->data_in); |
537 | devm_kfree(dev: kcs_bmc->dev, p: priv); |
538 | |
539 | return 0; |
540 | } |
541 | |
542 | static const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = { |
543 | .add_device = kcs_bmc_ipmi_add_device, |
544 | .remove_device = kcs_bmc_ipmi_remove_device, |
545 | }; |
546 | |
547 | static struct kcs_bmc_driver kcs_bmc_ipmi_driver = { |
548 | .ops = &kcs_bmc_ipmi_driver_ops, |
549 | }; |
550 | |
551 | static int __init kcs_bmc_ipmi_init(void) |
552 | { |
553 | kcs_bmc_register_driver(drv: &kcs_bmc_ipmi_driver); |
554 | |
555 | return 0; |
556 | } |
557 | module_init(kcs_bmc_ipmi_init); |
558 | |
559 | static void __exit kcs_bmc_ipmi_exit(void) |
560 | { |
561 | kcs_bmc_unregister_driver(drv: &kcs_bmc_ipmi_driver); |
562 | } |
563 | module_exit(kcs_bmc_ipmi_exit); |
564 | |
565 | MODULE_LICENSE("GPL v2" ); |
566 | MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>" ); |
567 | MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>" ); |
568 | MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software" ); |
569 | |