704 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Intel ECLite opregion driver for talking to ECLite firmware running on4 * Intel Integrated Sensor Hub (ISH) using ISH Transport Protocol (ISHTP)5 *6 * Copyright (c) 2021, Intel Corporation.7 */8 9#include <linux/acpi.h>10#include <linux/bitops.h>11#include <linux/device.h>12#include <linux/errno.h>13#include <linux/intel-ish-client-if.h>14#include <linux/kernel.h>15#include <linux/module.h>16#include <linux/mutex.h>17#include <linux/slab.h>18#include <linux/suspend.h>19#include <linux/types.h>20#include <linux/uuid.h>21#include <linux/uaccess.h>22 23#define ECLITE_DATA_OPREGION_ID 0x9E24#define ECLITE_CMD_OPREGION_ID 0x9F25 26#define ECL_MSG_DATA 0x127#define ECL_MSG_EVENT 0x228 29#define ECL_ISH_READ 0x130#define ECL_ISH_WRITE 0x231#define ECL_ISH_HEADER_VERSION 032 33#define ECL_CL_RX_RING_SIZE 1634#define ECL_CL_TX_RING_SIZE 835 36#define ECL_DATA_OPR_BUFLEN 38437#define ECL_EVENTS_NOTIFY 33338 39#define cmd_opr_offsetof(element) offsetof(struct opregion_cmd, element)40#define cl_data_to_dev(opr_dev) ishtp_device((opr_dev)->cl_device)41 42#ifndef BITS_TO_BYTES43#define BITS_TO_BYTES(x) ((x) / 8)44#endif45 46struct opregion_cmd {47 unsigned int command;48 unsigned int offset;49 unsigned int length;50 unsigned int event_id;51};52 53struct opregion_data {54 char data[ECL_DATA_OPR_BUFLEN];55};56 57struct opregion_context {58 struct opregion_cmd cmd_area;59 struct opregion_data data_area;60};61 62struct ecl_message_header {63 unsigned int version:2;64 unsigned int data_type:2;65 unsigned int request_type:2;66 unsigned int offset:9;67 unsigned int data_len:9;68 unsigned int event:8;69};70 71struct ecl_message {72 struct ecl_message_header header;73 char payload[ECL_DATA_OPR_BUFLEN];74};75 76struct ishtp_opregion_dev {77 struct opregion_context opr_context;78 struct ishtp_cl *ecl_ishtp_cl;79 struct ishtp_cl_device *cl_device;80 struct ishtp_fw_client *fw_client;81 struct ishtp_cl_rb *rb;82 struct acpi_device *adev;83 unsigned int dsm_event_id;84 unsigned int ish_link_ready;85 unsigned int ish_read_done;86 unsigned int acpi_init_done;87 wait_queue_head_t read_wait;88 struct work_struct event_work;89 struct work_struct reset_work;90 /* lock for opregion context */91 struct mutex lock;92 93};94 95/* eclite ishtp client UUID: 6a19cc4b-d760-4de3-b14d-f25ebd0fbcd9 */96static const struct ishtp_device_id ecl_ishtp_id_table[] = {97 { .guid = GUID_INIT(0x6a19cc4b, 0xd760, 0x4de3,98 0xb1, 0x4d, 0xf2, 0x5e, 0xbd, 0xf, 0xbc, 0xd9), },99 { }100};101MODULE_DEVICE_TABLE(ishtp, ecl_ishtp_id_table);102 103/* ACPI DSM UUID: 91d936a7-1f01-49c6-a6b4-72f00ad8d8a5 */104static const guid_t ecl_acpi_guid =105 GUID_INIT(0x91d936a7, 0x1f01, 0x49c6, 0xa6,106 0xb4, 0x72, 0xf0, 0x0a, 0xd8, 0xd8, 0xa5);107 108/**109 * ecl_ish_cl_read() - Read data from eclite FW110 *111 * @opr_dev: pointer to opregion device112 *113 * This function issues a read request to eclite FW and waits until it114 * receives a response. When response is received the read data is copied to115 * opregion buffer.116 */117static int ecl_ish_cl_read(struct ishtp_opregion_dev *opr_dev)118{119 struct ecl_message_header header;120 int len, rv;121 122 if (!opr_dev->ish_link_ready)123 return -EIO;124 125 if ((opr_dev->opr_context.cmd_area.offset +126 opr_dev->opr_context.cmd_area.length) > ECL_DATA_OPR_BUFLEN) {127 return -EINVAL;128 }129 130 header.version = ECL_ISH_HEADER_VERSION;131 header.data_type = ECL_MSG_DATA;132 header.request_type = ECL_ISH_READ;133 header.offset = opr_dev->opr_context.cmd_area.offset;134 header.data_len = opr_dev->opr_context.cmd_area.length;135 header.event = opr_dev->opr_context.cmd_area.event_id;136 len = sizeof(header);137 138 opr_dev->ish_read_done = false;139 rv = ishtp_cl_send(opr_dev->ecl_ishtp_cl, (uint8_t *)&header, len);140 if (rv) {141 dev_err(cl_data_to_dev(opr_dev), "ish-read : send failed\n");142 return -EIO;143 }144 145 dev_dbg(cl_data_to_dev(opr_dev),146 "[ish_rd] Req: off : %x, len : %x\n",147 header.offset,148 header.data_len);149 150 rv = wait_event_interruptible_timeout(opr_dev->read_wait,151 opr_dev->ish_read_done,152 2 * HZ);153 if (!rv) {154 dev_err(cl_data_to_dev(opr_dev),155 "[ish_rd] No response from firmware\n");156 return -EIO;157 }158 159 return 0;160}161 162/**163 * ecl_ish_cl_write() - This function writes data to eclite FW.164 *165 * @opr_dev: pointer to opregion device166 *167 * This function writes data to eclite FW.168 */169static int ecl_ish_cl_write(struct ishtp_opregion_dev *opr_dev)170{171 struct ecl_message message;172 int len;173 174 if (!opr_dev->ish_link_ready)175 return -EIO;176 177 if ((opr_dev->opr_context.cmd_area.offset +178 opr_dev->opr_context.cmd_area.length) > ECL_DATA_OPR_BUFLEN) {179 return -EINVAL;180 }181 182 message.header.version = ECL_ISH_HEADER_VERSION;183 message.header.data_type = ECL_MSG_DATA;184 message.header.request_type = ECL_ISH_WRITE;185 message.header.offset = opr_dev->opr_context.cmd_area.offset;186 message.header.data_len = opr_dev->opr_context.cmd_area.length;187 message.header.event = opr_dev->opr_context.cmd_area.event_id;188 len = sizeof(struct ecl_message_header) + message.header.data_len;189 190 memcpy(message.payload,191 opr_dev->opr_context.data_area.data + message.header.offset,192 message.header.data_len);193 194 dev_dbg(cl_data_to_dev(opr_dev),195 "[ish_wr] off : %x, len : %x\n",196 message.header.offset,197 message.header.data_len);198 199 return ishtp_cl_send(opr_dev->ecl_ishtp_cl, (uint8_t *)&message, len);200}201 202static acpi_status203ecl_opregion_cmd_handler(u32 function, acpi_physical_address address,204 u32 bits, u64 *value64,205 void *handler_context, void *region_context)206{207 struct ishtp_opregion_dev *opr_dev;208 struct opregion_cmd *cmd;209 acpi_status status = AE_OK;210 211 if (!region_context || !value64)212 return AE_BAD_PARAMETER;213 214 if (function == ACPI_READ)215 return AE_ERROR;216 217 opr_dev = (struct ishtp_opregion_dev *)region_context;218 219 mutex_lock(&opr_dev->lock);220 221 cmd = &opr_dev->opr_context.cmd_area;222 223 switch (address) {224 case cmd_opr_offsetof(command):225 cmd->command = (u32)*value64;226 227 if (cmd->command == ECL_ISH_READ)228 status = ecl_ish_cl_read(opr_dev);229 else if (cmd->command == ECL_ISH_WRITE)230 status = ecl_ish_cl_write(opr_dev);231 else232 status = AE_ERROR;233 break;234 case cmd_opr_offsetof(offset):235 cmd->offset = (u32)*value64;236 break;237 case cmd_opr_offsetof(length):238 cmd->length = (u32)*value64;239 break;240 case cmd_opr_offsetof(event_id):241 cmd->event_id = (u32)*value64;242 break;243 default:244 status = AE_ERROR;245 }246 247 mutex_unlock(&opr_dev->lock);248 249 return status;250}251 252static acpi_status253ecl_opregion_data_handler(u32 function, acpi_physical_address address,254 u32 bits, u64 *value64,255 void *handler_context, void *region_context)256{257 struct ishtp_opregion_dev *opr_dev;258 unsigned int bytes = BITS_TO_BYTES(bits);259 void *data_addr;260 261 if (!region_context || !value64)262 return AE_BAD_PARAMETER;263 264 if (address + bytes > ECL_DATA_OPR_BUFLEN)265 return AE_BAD_PARAMETER;266 267 opr_dev = (struct ishtp_opregion_dev *)region_context;268 269 mutex_lock(&opr_dev->lock);270 271 data_addr = &opr_dev->opr_context.data_area.data[address];272 273 if (function == ACPI_READ) {274 memcpy(value64, data_addr, bytes);275 } else if (function == ACPI_WRITE) {276 memcpy(data_addr, value64, bytes);277 } else {278 mutex_unlock(&opr_dev->lock);279 return AE_BAD_PARAMETER;280 }281 282 mutex_unlock(&opr_dev->lock);283 284 return AE_OK;285}286 287static int acpi_find_eclite_device(struct ishtp_opregion_dev *opr_dev)288{289 struct acpi_device *adev;290 291 /* Find ECLite device and save reference */292 adev = acpi_dev_get_first_match_dev("INTC1035", NULL, -1);293 if (!adev) {294 dev_err(cl_data_to_dev(opr_dev), "eclite ACPI device not found\n");295 return -ENODEV;296 }297 298 opr_dev->adev = adev;299 300 return 0;301}302 303static int acpi_opregion_init(struct ishtp_opregion_dev *opr_dev)304{305 acpi_status status;306 307 status = acpi_install_address_space_handler(opr_dev->adev->handle,308 ECLITE_CMD_OPREGION_ID,309 ecl_opregion_cmd_handler,310 NULL, opr_dev);311 if (ACPI_FAILURE(status)) {312 dev_err(cl_data_to_dev(opr_dev),313 "cmd space handler install failed\n");314 return -ENODEV;315 }316 317 status = acpi_install_address_space_handler(opr_dev->adev->handle,318 ECLITE_DATA_OPREGION_ID,319 ecl_opregion_data_handler,320 NULL, opr_dev);321 if (ACPI_FAILURE(status)) {322 dev_err(cl_data_to_dev(opr_dev),323 "data space handler install failed\n");324 325 acpi_remove_address_space_handler(opr_dev->adev->handle,326 ECLITE_CMD_OPREGION_ID,327 ecl_opregion_cmd_handler);328 return -ENODEV;329 }330 opr_dev->acpi_init_done = true;331 332 dev_dbg(cl_data_to_dev(opr_dev), "Opregion handlers are installed\n");333 334 return 0;335}336 337static void acpi_opregion_deinit(struct ishtp_opregion_dev *opr_dev)338{339 acpi_remove_address_space_handler(opr_dev->adev->handle,340 ECLITE_CMD_OPREGION_ID,341 ecl_opregion_cmd_handler);342 343 acpi_remove_address_space_handler(opr_dev->adev->handle,344 ECLITE_DATA_OPREGION_ID,345 ecl_opregion_data_handler);346 opr_dev->acpi_init_done = false;347}348 349static void ecl_acpi_invoke_dsm(struct work_struct *work)350{351 struct ishtp_opregion_dev *opr_dev;352 union acpi_object *obj;353 354 opr_dev = container_of(work, struct ishtp_opregion_dev, event_work);355 if (!opr_dev->acpi_init_done)356 return;357 358 obj = acpi_evaluate_dsm(opr_dev->adev->handle, &ecl_acpi_guid, 0,359 opr_dev->dsm_event_id, NULL);360 if (!obj) {361 dev_warn(cl_data_to_dev(opr_dev), "_DSM fn call failed\n");362 return;363 }364 365 dev_dbg(cl_data_to_dev(opr_dev), "Exec DSM function code: %d success\n",366 opr_dev->dsm_event_id);367 368 ACPI_FREE(obj);369}370 371static void ecl_ish_process_rx_data(struct ishtp_opregion_dev *opr_dev)372{373 struct ecl_message *message =374 (struct ecl_message *)opr_dev->rb->buffer.data;375 376 dev_dbg(cl_data_to_dev(opr_dev),377 "[ish_rd] Resp: off : %x, len : %x\n",378 message->header.offset,379 message->header.data_len);380 381 if ((message->header.offset + message->header.data_len) >382 ECL_DATA_OPR_BUFLEN) {383 return;384 }385 386 memcpy(opr_dev->opr_context.data_area.data + message->header.offset,387 message->payload, message->header.data_len);388 389 opr_dev->ish_read_done = true;390 wake_up_interruptible(&opr_dev->read_wait);391}392 393static void ecl_ish_process_rx_event(struct ishtp_opregion_dev *opr_dev)394{395 struct ecl_message_header *header =396 (struct ecl_message_header *)opr_dev->rb->buffer.data;397 398 dev_dbg(cl_data_to_dev(opr_dev),399 "[ish_ev] Evt received: %8x\n", header->event);400 401 opr_dev->dsm_event_id = header->event;402 schedule_work(&opr_dev->event_work);403}404 405static int ecl_ish_cl_enable_events(struct ishtp_opregion_dev *opr_dev,406 bool config_enable)407{408 struct ecl_message message;409 int len;410 411 message.header.version = ECL_ISH_HEADER_VERSION;412 message.header.data_type = ECL_MSG_DATA;413 message.header.request_type = ECL_ISH_WRITE;414 message.header.offset = ECL_EVENTS_NOTIFY;415 message.header.data_len = 1;416 message.payload[0] = config_enable;417 418 len = sizeof(struct ecl_message_header) + message.header.data_len;419 420 return ishtp_cl_send(opr_dev->ecl_ishtp_cl, (uint8_t *)&message, len);421}422 423static void ecl_ishtp_cl_event_cb(struct ishtp_cl_device *cl_device)424{425 struct ishtp_cl *ecl_ishtp_cl = ishtp_get_drvdata(cl_device);426 struct ishtp_opregion_dev *opr_dev;427 struct ecl_message_header *header;428 struct ishtp_cl_rb *rb;429 430 opr_dev = ishtp_get_client_data(ecl_ishtp_cl);431 while ((rb = ishtp_cl_rx_get_rb(opr_dev->ecl_ishtp_cl)) != NULL) {432 opr_dev->rb = rb;433 header = (struct ecl_message_header *)rb->buffer.data;434 435 if (header->data_type == ECL_MSG_DATA)436 ecl_ish_process_rx_data(opr_dev);437 else if (header->data_type == ECL_MSG_EVENT)438 ecl_ish_process_rx_event(opr_dev);439 else440 /* Got an event with wrong data_type, ignore it */441 dev_err(cl_data_to_dev(opr_dev),442 "[ish_cb] Received wrong data_type\n");443 444 ishtp_cl_io_rb_recycle(rb);445 }446}447 448static int ecl_ishtp_cl_init(struct ishtp_cl *ecl_ishtp_cl)449{450 struct ishtp_opregion_dev *opr_dev =451 ishtp_get_client_data(ecl_ishtp_cl);452 struct ishtp_fw_client *fw_client;453 struct ishtp_device *dev;454 int rv;455 456 rv = ishtp_cl_link(ecl_ishtp_cl);457 if (rv) {458 dev_err(cl_data_to_dev(opr_dev), "ishtp_cl_link failed\n");459 return rv;460 }461 462 dev = ishtp_get_ishtp_device(ecl_ishtp_cl);463 464 /* Connect to FW client */465 ishtp_set_tx_ring_size(ecl_ishtp_cl, ECL_CL_TX_RING_SIZE);466 ishtp_set_rx_ring_size(ecl_ishtp_cl, ECL_CL_RX_RING_SIZE);467 468 fw_client = ishtp_fw_cl_get_client(dev, &ecl_ishtp_id_table[0].guid);469 if (!fw_client) {470 dev_err(cl_data_to_dev(opr_dev), "fw client not found\n");471 return -ENOENT;472 }473 474 ishtp_cl_set_fw_client_id(ecl_ishtp_cl,475 ishtp_get_fw_client_id(fw_client));476 477 ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_CONNECTING);478 479 rv = ishtp_cl_connect(ecl_ishtp_cl);480 if (rv) {481 dev_err(cl_data_to_dev(opr_dev), "client connect failed\n");482 483 ishtp_cl_unlink(ecl_ishtp_cl);484 return rv;485 }486 487 dev_dbg(cl_data_to_dev(opr_dev), "Host connected to fw client\n");488 489 return 0;490}491 492static void ecl_ishtp_cl_deinit(struct ishtp_cl *ecl_ishtp_cl)493{494 ishtp_cl_unlink(ecl_ishtp_cl);495 ishtp_cl_flush_queues(ecl_ishtp_cl);496 ishtp_cl_free(ecl_ishtp_cl);497}498 499static void ecl_ishtp_cl_reset_handler(struct work_struct *work)500{501 struct ishtp_opregion_dev *opr_dev;502 struct ishtp_cl_device *cl_device;503 struct ishtp_cl *ecl_ishtp_cl;504 int rv;505 int retry;506 507 opr_dev = container_of(work, struct ishtp_opregion_dev, reset_work);508 509 opr_dev->ish_link_ready = false;510 511 cl_device = opr_dev->cl_device;512 ecl_ishtp_cl = opr_dev->ecl_ishtp_cl;513 514 ecl_ishtp_cl_deinit(ecl_ishtp_cl);515 516 ecl_ishtp_cl = ishtp_cl_allocate(cl_device);517 if (!ecl_ishtp_cl)518 return;519 520 ishtp_set_drvdata(cl_device, ecl_ishtp_cl);521 ishtp_set_client_data(ecl_ishtp_cl, opr_dev);522 523 opr_dev->ecl_ishtp_cl = ecl_ishtp_cl;524 525 for (retry = 0; retry < 3; ++retry) {526 rv = ecl_ishtp_cl_init(ecl_ishtp_cl);527 if (!rv)528 break;529 }530 if (rv) {531 ishtp_cl_free(ecl_ishtp_cl);532 opr_dev->ecl_ishtp_cl = NULL;533 dev_err(cl_data_to_dev(opr_dev),534 "[ish_rst] Reset failed. Link not ready.\n");535 return;536 }537 538 ishtp_register_event_cb(cl_device, ecl_ishtp_cl_event_cb);539 dev_info(cl_data_to_dev(opr_dev),540 "[ish_rst] Reset Success. Link ready.\n");541 542 opr_dev->ish_link_ready = true;543 544 if (opr_dev->acpi_init_done)545 return;546 547 rv = acpi_opregion_init(opr_dev);548 if (rv) {549 dev_err(cl_data_to_dev(opr_dev),550 "ACPI opregion init failed\n");551 }552}553 554static int ecl_ishtp_cl_probe(struct ishtp_cl_device *cl_device)555{556 struct ishtp_cl *ecl_ishtp_cl;557 struct ishtp_opregion_dev *opr_dev;558 int rv;559 560 opr_dev = devm_kzalloc(ishtp_device(cl_device), sizeof(*opr_dev),561 GFP_KERNEL);562 if (!opr_dev)563 return -ENOMEM;564 565 ecl_ishtp_cl = ishtp_cl_allocate(cl_device);566 if (!ecl_ishtp_cl)567 return -ENOMEM;568 569 ishtp_set_drvdata(cl_device, ecl_ishtp_cl);570 ishtp_set_client_data(ecl_ishtp_cl, opr_dev);571 opr_dev->ecl_ishtp_cl = ecl_ishtp_cl;572 opr_dev->cl_device = cl_device;573 574 init_waitqueue_head(&opr_dev->read_wait);575 INIT_WORK(&opr_dev->event_work, ecl_acpi_invoke_dsm);576 INIT_WORK(&opr_dev->reset_work, ecl_ishtp_cl_reset_handler);577 578 /* Initialize ish client device */579 rv = ecl_ishtp_cl_init(ecl_ishtp_cl);580 if (rv) {581 dev_err(cl_data_to_dev(opr_dev), "Client init failed\n");582 goto err_exit;583 }584 585 dev_dbg(cl_data_to_dev(opr_dev), "eclite-ishtp client initialised\n");586 587 opr_dev->ish_link_ready = true;588 mutex_init(&opr_dev->lock);589 590 rv = acpi_find_eclite_device(opr_dev);591 if (rv) {592 dev_err(cl_data_to_dev(opr_dev), "ECLite ACPI ID not found\n");593 goto err_exit;594 }595 596 /* Register a handler for eclite fw events */597 ishtp_register_event_cb(cl_device, ecl_ishtp_cl_event_cb);598 599 /* Now init opregion handlers */600 rv = acpi_opregion_init(opr_dev);601 if (rv) {602 dev_err(cl_data_to_dev(opr_dev), "ACPI opregion init failed\n");603 goto err_exit;604 }605 606 /* Reprobe devices depending on ECLite - battery, fan, etc. */607 acpi_dev_clear_dependencies(opr_dev->adev);608 609 return 0;610err_exit:611 ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING);612 ishtp_cl_disconnect(ecl_ishtp_cl);613 ecl_ishtp_cl_deinit(ecl_ishtp_cl);614 615 return rv;616}617 618static void ecl_ishtp_cl_remove(struct ishtp_cl_device *cl_device)619{620 struct ishtp_cl *ecl_ishtp_cl = ishtp_get_drvdata(cl_device);621 struct ishtp_opregion_dev *opr_dev =622 ishtp_get_client_data(ecl_ishtp_cl);623 624 if (opr_dev->acpi_init_done)625 acpi_opregion_deinit(opr_dev);626 627 acpi_dev_put(opr_dev->adev);628 629 ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING);630 ishtp_cl_disconnect(ecl_ishtp_cl);631 ecl_ishtp_cl_deinit(ecl_ishtp_cl);632 633 cancel_work_sync(&opr_dev->reset_work);634 cancel_work_sync(&opr_dev->event_work);635}636 637static int ecl_ishtp_cl_reset(struct ishtp_cl_device *cl_device)638{639 struct ishtp_cl *ecl_ishtp_cl = ishtp_get_drvdata(cl_device);640 struct ishtp_opregion_dev *opr_dev =641 ishtp_get_client_data(ecl_ishtp_cl);642 643 schedule_work(&opr_dev->reset_work);644 645 return 0;646}647 648static int ecl_ishtp_cl_suspend(struct device *device)649{650 struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device);651 struct ishtp_cl *ecl_ishtp_cl = ishtp_get_drvdata(cl_device);652 struct ishtp_opregion_dev *opr_dev =653 ishtp_get_client_data(ecl_ishtp_cl);654 655 if (acpi_target_system_state() == ACPI_STATE_S0)656 return 0;657 658 acpi_opregion_deinit(opr_dev);659 ecl_ish_cl_enable_events(opr_dev, false);660 661 return 0;662}663 664static int ecl_ishtp_cl_resume(struct device *device)665{666 /* A reset is expected to call after an Sx. At this point667 * we are not sure if the link is up or not to restore anything,668 * so do nothing in resume path669 */670 return 0;671}672 673static const struct dev_pm_ops ecl_ishtp_pm_ops = {674 .suspend = ecl_ishtp_cl_suspend,675 .resume = ecl_ishtp_cl_resume,676};677 678static struct ishtp_cl_driver ecl_ishtp_cl_driver = {679 .name = "ishtp-eclite",680 .id = ecl_ishtp_id_table,681 .probe = ecl_ishtp_cl_probe,682 .remove = ecl_ishtp_cl_remove,683 .reset = ecl_ishtp_cl_reset,684 .driver.pm = &ecl_ishtp_pm_ops,685};686 687static int __init ecl_ishtp_init(void)688{689 return ishtp_cl_driver_register(&ecl_ishtp_cl_driver, THIS_MODULE);690}691 692static void __exit ecl_ishtp_exit(void)693{694 return ishtp_cl_driver_unregister(&ecl_ishtp_cl_driver);695}696 697late_initcall(ecl_ishtp_init);698module_exit(ecl_ishtp_exit);699 700MODULE_DESCRIPTION("ISH ISHTP eclite client opregion driver");701MODULE_AUTHOR("K Naduvalath, Sumesh <sumesh.k.naduvalath@intel.com>");702 703MODULE_LICENSE("GPL v2");704