|  | /* | 
|  | * | 
|  | * Intel Management Engine Interface (Intel MEI) Linux driver | 
|  | * Copyright (c) 2003-2012, Intel Corporation. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms and conditions of the GNU General Public License, | 
|  | * version 2, as published by the Free Software Foundation. | 
|  | * | 
|  | * This program is distributed in the hope it will be useful, but WITHOUT | 
|  | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|  | * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|  | * more details. | 
|  | * | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include <linux/pci.h> | 
|  | #include <linux/kthread.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/fs.h> | 
|  | #include <linux/jiffies.h> | 
|  |  | 
|  | #include "mei_dev.h" | 
|  | #include <linux/mei.h> | 
|  | #include "hw.h" | 
|  | #include "interface.h" | 
|  |  | 
|  |  | 
|  | /** | 
|  | * mei_interrupt_quick_handler - The ISR of the MEI device | 
|  | * | 
|  | * @irq: The irq number | 
|  | * @dev_id: pointer to the device structure | 
|  | * | 
|  | * returns irqreturn_t | 
|  | */ | 
|  | irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) | 
|  | { | 
|  | struct mei_device *dev = (struct mei_device *) dev_id; | 
|  | u32 csr_reg = mei_hcsr_read(dev); | 
|  |  | 
|  | if ((csr_reg & H_IS) != H_IS) | 
|  | return IRQ_NONE; | 
|  |  | 
|  | /* clear H_IS bit in H_CSR */ | 
|  | mei_reg_write(dev, H_CSR, csr_reg); | 
|  |  | 
|  | return IRQ_WAKE_THREAD; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_cmpl - processes completed operation. | 
|  | * | 
|  | * @cl: private data of the file object. | 
|  | * @cb_pos: callback block. | 
|  | */ | 
|  | static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) | 
|  | { | 
|  | if (cb_pos->major_file_operations == MEI_WRITE) { | 
|  | mei_free_cb_private(cb_pos); | 
|  | cb_pos = NULL; | 
|  | cl->writing_state = MEI_WRITE_COMPLETE; | 
|  | if (waitqueue_active(&cl->tx_wait)) | 
|  | wake_up_interruptible(&cl->tx_wait); | 
|  |  | 
|  | } else if (cb_pos->major_file_operations == MEI_READ && | 
|  | MEI_READING == cl->reading_state) { | 
|  | cl->reading_state = MEI_READ_COMPLETE; | 
|  | if (waitqueue_active(&cl->rx_wait)) | 
|  | wake_up_interruptible(&cl->rx_wait); | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_cmpl_iamthif - processes completed iamthif operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @cb_pos: callback block. | 
|  | */ | 
|  | static void _mei_cmpl_iamthif(struct mei_device *dev, struct mei_cl_cb *cb_pos) | 
|  | { | 
|  | if (dev->iamthif_canceled != 1) { | 
|  | dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; | 
|  | dev->iamthif_stall_timer = 0; | 
|  | memcpy(cb_pos->response_buffer.data, | 
|  | dev->iamthif_msg_buf, | 
|  | dev->iamthif_msg_buf_index); | 
|  | list_add_tail(&cb_pos->cb_list, | 
|  | &dev->amthi_read_complete_list.mei_cb.cb_list); | 
|  | dev_dbg(&dev->pdev->dev, "amthi read completed.\n"); | 
|  | dev->iamthif_timer = jiffies; | 
|  | dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", | 
|  | dev->iamthif_timer); | 
|  | } else { | 
|  | mei_run_next_iamthif_cmd(dev); | 
|  | } | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); | 
|  | wake_up_interruptible(&dev->iamthif_cl.wait); | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * mei_irq_thread_read_amthi_message - bottom half read routine after ISR to | 
|  | * handle the read amthi message data processing. | 
|  | * | 
|  | * @complete_list: An instance of our list structure | 
|  | * @dev: the device structure | 
|  | * @mei_hdr: header of amthi message | 
|  | * | 
|  | * returns 0 on success, <0 on failure. | 
|  | */ | 
|  | static int mei_irq_thread_read_amthi_message(struct mei_io_list *complete_list, | 
|  | struct mei_device *dev, | 
|  | struct mei_msg_hdr *mei_hdr) | 
|  | { | 
|  | struct mei_cl *cl; | 
|  | struct mei_cl_cb *cb; | 
|  | unsigned char *buffer; | 
|  |  | 
|  | BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); | 
|  | BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); | 
|  |  | 
|  | buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; | 
|  | BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); | 
|  |  | 
|  | mei_read_slots(dev, buffer, mei_hdr->length); | 
|  |  | 
|  | dev->iamthif_msg_buf_index += mei_hdr->length; | 
|  |  | 
|  | if (!mei_hdr->msg_complete) | 
|  | return 0; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "amthi_message_buffer_index =%d\n", | 
|  | mei_hdr->length); | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); | 
|  | if (!dev->iamthif_current_cb) | 
|  | return -ENODEV; | 
|  |  | 
|  | cb = dev->iamthif_current_cb; | 
|  | dev->iamthif_current_cb = NULL; | 
|  |  | 
|  | cl = (struct mei_cl *)cb->file_private; | 
|  | if (!cl) | 
|  | return -ENODEV; | 
|  |  | 
|  | dev->iamthif_stall_timer = 0; | 
|  | cb->information =	dev->iamthif_msg_buf_index; | 
|  | cb->read_time = jiffies; | 
|  | if (dev->iamthif_ioctl && cl == &dev->iamthif_cl) { | 
|  | /* found the iamthif cb */ | 
|  | dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); | 
|  | dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); | 
|  | list_add_tail(&cb->cb_list, | 
|  | &complete_list->mei_cb.cb_list); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_state_ok - checks if mei header matches file private data | 
|  | * | 
|  | * @cl: private data of the file object | 
|  | * @mei_hdr: header of mei client message | 
|  | * | 
|  | * returns !=0 if matches, 0 if no match. | 
|  | */ | 
|  | static int _mei_irq_thread_state_ok(struct mei_cl *cl, | 
|  | struct mei_msg_hdr *mei_hdr) | 
|  | { | 
|  | return (cl->host_client_id == mei_hdr->host_addr && | 
|  | cl->me_client_id == mei_hdr->me_addr && | 
|  | cl->state == MEI_FILE_CONNECTED && | 
|  | MEI_READ_COMPLETE != cl->reading_state); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_irq_thread_read_client_message - bottom half read routine after ISR to | 
|  | * handle the read mei client message data processing. | 
|  | * | 
|  | * @complete_list: An instance of our list structure | 
|  | * @dev: the device structure | 
|  | * @mei_hdr: header of mei client message | 
|  | * | 
|  | * returns 0 on success, <0 on failure. | 
|  | */ | 
|  | static int mei_irq_thread_read_client_message(struct mei_io_list *complete_list, | 
|  | struct mei_device *dev, | 
|  | struct mei_msg_hdr *mei_hdr) | 
|  | { | 
|  | struct mei_cl *cl; | 
|  | struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; | 
|  | unsigned char *buffer = NULL; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "start client msg\n"); | 
|  | if (list_empty(&dev->read_list.mei_cb.cb_list)) | 
|  | goto quit; | 
|  |  | 
|  | list_for_each_entry_safe(cb_pos, cb_next, | 
|  | &dev->read_list.mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *)cb_pos->file_private; | 
|  | if (cl && _mei_irq_thread_state_ok(cl, mei_hdr)) { | 
|  | cl->reading_state = MEI_READING; | 
|  | buffer = cb_pos->response_buffer.data + cb_pos->information; | 
|  |  | 
|  | if (cb_pos->response_buffer.size < | 
|  | mei_hdr->length + cb_pos->information) { | 
|  | dev_dbg(&dev->pdev->dev, "message overflow.\n"); | 
|  | list_del(&cb_pos->cb_list); | 
|  | return -ENOMEM; | 
|  | } | 
|  | if (buffer) | 
|  | mei_read_slots(dev, buffer, mei_hdr->length); | 
|  |  | 
|  | cb_pos->information += mei_hdr->length; | 
|  | if (mei_hdr->msg_complete) { | 
|  | cl->status = 0; | 
|  | list_del(&cb_pos->cb_list); | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "completed read host client = %d," | 
|  | "ME client = %d, " | 
|  | "data length = %lu\n", | 
|  | cl->host_client_id, | 
|  | cl->me_client_id, | 
|  | cb_pos->information); | 
|  |  | 
|  | *(cb_pos->response_buffer.data + | 
|  | cb_pos->information) = '\0'; | 
|  | dev_dbg(&dev->pdev->dev, "cb_pos->res_buffer - %s\n", | 
|  | cb_pos->response_buffer.data); | 
|  | list_add_tail(&cb_pos->cb_list, | 
|  | &complete_list->mei_cb.cb_list); | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | quit: | 
|  | dev_dbg(&dev->pdev->dev, "message read\n"); | 
|  | if (!buffer) { | 
|  | mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); | 
|  | dev_dbg(&dev->pdev->dev, "discarding message, header =%08x.\n", | 
|  | *(u32 *) dev->rd_msg_buf); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_iamthif_read - prepares to read iamthif data. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_iamthif_read(struct mei_device *dev, s32 *slots) | 
|  | { | 
|  |  | 
|  | if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) | 
|  | + sizeof(struct hbm_flow_control))) { | 
|  | return -EMSGSIZE; | 
|  | } | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_flow_control) + 3) / 4; | 
|  | if (mei_send_flow_control(dev, &dev->iamthif_cl)) { | 
|  | dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "iamthif flow control success\n"); | 
|  | dev->iamthif_state = MEI_IAMTHIF_READING; | 
|  | dev->iamthif_flow_control_pending = false; | 
|  | dev->iamthif_msg_buf_index = 0; | 
|  | dev->iamthif_msg_buf_size = 0; | 
|  | dev->iamthif_stall_timer = IAMTHIF_STALL_TIMER; | 
|  | dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_close - processes close related operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * @cb_pos: callback block. | 
|  | * @cl: private data of the file object. | 
|  | * @cmpl_list: complete list. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, | 
|  | struct mei_cl_cb *cb_pos, | 
|  | struct mei_cl *cl, | 
|  | struct mei_io_list *cmpl_list) | 
|  | { | 
|  | if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_client_disconnect_request))) { | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_client_disconnect_request) + 3) / 4; | 
|  |  | 
|  | if (mei_disconnect(dev, cl)) { | 
|  | cl->status = 0; | 
|  | cb_pos->information = 0; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &cmpl_list->mei_cb.cb_list); | 
|  | return -EMSGSIZE; | 
|  | } else { | 
|  | cl->state = MEI_FILE_DISCONNECTING; | 
|  | cl->status = 0; | 
|  | cb_pos->information = 0; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &dev->ctrl_rd_list.mei_cb.cb_list); | 
|  | cl->timer_count = MEI_CONNECT_TIMEOUT; | 
|  | } | 
|  | } else { | 
|  | /* return the cancel routine */ | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * is_treat_specially_client - checks if the message belongs | 
|  | * to the file private data. | 
|  | * | 
|  | * @cl: private data of the file object | 
|  | * @rs: connect response bus message | 
|  | * | 
|  | */ | 
|  | static bool is_treat_specially_client(struct mei_cl *cl, | 
|  | struct hbm_client_connect_response *rs) | 
|  | { | 
|  |  | 
|  | if (cl->host_client_id == rs->host_addr && | 
|  | cl->me_client_id == rs->me_addr) { | 
|  | if (!rs->status) { | 
|  | cl->state = MEI_FILE_CONNECTED; | 
|  | cl->status = 0; | 
|  |  | 
|  | } else { | 
|  | cl->state = MEI_FILE_DISCONNECTED; | 
|  | cl->status = -ENODEV; | 
|  | } | 
|  | cl->timer_count = 0; | 
|  |  | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_client_connect_response - connects to response irq routine | 
|  | * | 
|  | * @dev: the device structure | 
|  | * @rs: connect response bus message | 
|  | */ | 
|  | static void mei_client_connect_response(struct mei_device *dev, | 
|  | struct hbm_client_connect_response *rs) | 
|  | { | 
|  |  | 
|  | struct mei_cl *cl; | 
|  | struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "connect_response:\n" | 
|  | "ME Client = %d\n" | 
|  | "Host Client = %d\n" | 
|  | "Status = %d\n", | 
|  | rs->me_addr, | 
|  | rs->host_addr, | 
|  | rs->status); | 
|  |  | 
|  | /* if WD or iamthif client treat specially */ | 
|  |  | 
|  | if (is_treat_specially_client(&(dev->wd_cl), rs)) { | 
|  | dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); | 
|  | mei_watchdog_register(dev); | 
|  |  | 
|  | /* next step in the state maching */ | 
|  | mei_host_init_iamthif(dev); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (is_treat_specially_client(&(dev->iamthif_cl), rs)) { | 
|  | dev->iamthif_state = MEI_IAMTHIF_IDLE; | 
|  | return; | 
|  | } | 
|  | list_for_each_entry_safe(cb_pos, cb_next, | 
|  | &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { | 
|  |  | 
|  | cl = (struct mei_cl *)cb_pos->file_private; | 
|  | if (!cl) { | 
|  | list_del(&cb_pos->cb_list); | 
|  | return; | 
|  | } | 
|  | if (MEI_IOCTL == cb_pos->major_file_operations) { | 
|  | if (is_treat_specially_client(cl, rs)) { | 
|  | list_del(&cb_pos->cb_list); | 
|  | cl->status = 0; | 
|  | cl->timer_count = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_client_disconnect_response - disconnects from response irq routine | 
|  | * | 
|  | * @dev: the device structure | 
|  | * @rs: disconnect response bus message | 
|  | */ | 
|  | static void mei_client_disconnect_response(struct mei_device *dev, | 
|  | struct hbm_client_connect_response *rs) | 
|  | { | 
|  | struct mei_cl *cl; | 
|  | struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "disconnect_response:\n" | 
|  | "ME Client = %d\n" | 
|  | "Host Client = %d\n" | 
|  | "Status = %d\n", | 
|  | rs->me_addr, | 
|  | rs->host_addr, | 
|  | rs->status); | 
|  |  | 
|  | list_for_each_entry_safe(cb_pos, cb_next, | 
|  | &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *)cb_pos->file_private; | 
|  |  | 
|  | if (!cl) { | 
|  | list_del(&cb_pos->cb_list); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); | 
|  | if (cl->host_client_id == rs->host_addr && | 
|  | cl->me_client_id == rs->me_addr) { | 
|  |  | 
|  | list_del(&cb_pos->cb_list); | 
|  | if (!rs->status) | 
|  | cl->state = MEI_FILE_DISCONNECTED; | 
|  |  | 
|  | cl->status = 0; | 
|  | cl->timer_count = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * same_flow_addr - tells if they have the same address. | 
|  | * | 
|  | * @file: private data of the file object. | 
|  | * @flow: flow control. | 
|  | * | 
|  | * returns  !=0, same; 0,not. | 
|  | */ | 
|  | static int same_flow_addr(struct mei_cl *cl, struct hbm_flow_control *flow) | 
|  | { | 
|  | return (cl->host_client_id == flow->host_addr && | 
|  | cl->me_client_id == flow->me_addr); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * add_single_flow_creds - adds single buffer credentials. | 
|  | * | 
|  | * @file: private data ot the file object. | 
|  | * @flow: flow control. | 
|  | */ | 
|  | static void add_single_flow_creds(struct mei_device *dev, | 
|  | struct hbm_flow_control *flow) | 
|  | { | 
|  | struct mei_me_client *client; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < dev->me_clients_num; i++) { | 
|  | client = &dev->me_clients[i]; | 
|  | if (client && flow->me_addr == client->client_id) { | 
|  | if (client->props.single_recv_buf) { | 
|  | client->mei_flow_ctrl_creds++; | 
|  | dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", | 
|  | flow->me_addr); | 
|  | dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", | 
|  | client->mei_flow_ctrl_creds); | 
|  | } else { | 
|  | BUG();	/* error in flow control */ | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_client_flow_control_response - flow control response irq routine | 
|  | * | 
|  | * @dev: the device structure | 
|  | * @flow_control: flow control response bus message | 
|  | */ | 
|  | static void mei_client_flow_control_response(struct mei_device *dev, | 
|  | struct hbm_flow_control *flow_control) | 
|  | { | 
|  | struct mei_cl *cl_pos = NULL; | 
|  | struct mei_cl *cl_next = NULL; | 
|  |  | 
|  | if (!flow_control->host_addr) { | 
|  | /* single receive buffer */ | 
|  | add_single_flow_creds(dev, flow_control); | 
|  | } else { | 
|  | /* normal connection */ | 
|  | list_for_each_entry_safe(cl_pos, cl_next, | 
|  | &dev->file_list, link) { | 
|  | dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in file_list\n"); | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "cl of host client %d ME client %d.\n", | 
|  | cl_pos->host_client_id, | 
|  | cl_pos->me_client_id); | 
|  | dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", | 
|  | flow_control->host_addr, | 
|  | flow_control->me_addr); | 
|  | if (same_flow_addr(cl_pos, flow_control)) { | 
|  | dev_dbg(&dev->pdev->dev, "recv ctrl msg for host  %d ME %d.\n", | 
|  | flow_control->host_addr, | 
|  | flow_control->me_addr); | 
|  | cl_pos->mei_flow_ctrl_creds++; | 
|  | dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n", | 
|  | cl_pos->mei_flow_ctrl_creds); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * same_disconn_addr - tells if they have the same address | 
|  | * | 
|  | * @file: private data of the file object. | 
|  | * @disconn: disconnection request. | 
|  | * | 
|  | * returns !=0, same; 0,not. | 
|  | */ | 
|  | static int same_disconn_addr(struct mei_cl *cl, | 
|  | struct hbm_client_disconnect_request *disconn) | 
|  | { | 
|  | return (cl->host_client_id == disconn->host_addr && | 
|  | cl->me_client_id == disconn->me_addr); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_client_disconnect_request - disconnects from request irq routine | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @disconnect_req: disconnect request bus message. | 
|  | */ | 
|  | static void mei_client_disconnect_request(struct mei_device *dev, | 
|  | struct hbm_client_disconnect_request *disconnect_req) | 
|  | { | 
|  | struct mei_msg_hdr *mei_hdr; | 
|  | struct hbm_client_connect_response *disconnect_res; | 
|  | struct mei_cl *cl_pos = NULL; | 
|  | struct mei_cl *cl_next = NULL; | 
|  |  | 
|  | list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { | 
|  | if (same_disconn_addr(cl_pos, disconnect_req)) { | 
|  | dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", | 
|  | disconnect_req->host_addr, | 
|  | disconnect_req->me_addr); | 
|  | cl_pos->state = MEI_FILE_DISCONNECTED; | 
|  | cl_pos->timer_count = 0; | 
|  | if (cl_pos == &dev->wd_cl) { | 
|  | dev->wd_due_counter = 0; | 
|  | dev->wd_pending = false; | 
|  | } else if (cl_pos == &dev->iamthif_cl) | 
|  | dev->iamthif_timer = 0; | 
|  |  | 
|  | /* prepare disconnect response */ | 
|  | mei_hdr = | 
|  | (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; | 
|  | mei_hdr->host_addr = 0; | 
|  | mei_hdr->me_addr = 0; | 
|  | mei_hdr->length = | 
|  | sizeof(struct hbm_client_connect_response); | 
|  | mei_hdr->msg_complete = 1; | 
|  | mei_hdr->reserved = 0; | 
|  |  | 
|  | disconnect_res = | 
|  | (struct hbm_client_connect_response *) | 
|  | &dev->ext_msg_buf[1]; | 
|  | disconnect_res->host_addr = cl_pos->host_client_id; | 
|  | disconnect_res->me_addr = cl_pos->me_client_id; | 
|  | disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; | 
|  | disconnect_res->status = 0; | 
|  | dev->extra_write_index = 2; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * mei_irq_thread_read_bus_message - bottom half read routine after ISR to | 
|  | * handle the read bus message cmd processing. | 
|  | * | 
|  | * @dev: the device structure | 
|  | * @mei_hdr: header of bus message | 
|  | */ | 
|  | static void mei_irq_thread_read_bus_message(struct mei_device *dev, | 
|  | struct mei_msg_hdr *mei_hdr) | 
|  | { | 
|  | struct mei_bus_message *mei_msg; | 
|  | struct hbm_host_version_response *version_res; | 
|  | struct hbm_client_connect_response *connect_res; | 
|  | struct hbm_client_connect_response *disconnect_res; | 
|  | struct hbm_flow_control *flow_control; | 
|  | struct hbm_props_response *props_res; | 
|  | struct hbm_host_enum_response *enum_res; | 
|  | struct hbm_client_disconnect_request *disconnect_req; | 
|  | struct hbm_host_stop_request *host_stop_req; | 
|  | int res; | 
|  |  | 
|  |  | 
|  | /* read the message to our buffer */ | 
|  | BUG_ON(mei_hdr->length >= sizeof(dev->rd_msg_buf)); | 
|  | mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); | 
|  | mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; | 
|  |  | 
|  | switch (mei_msg->hbm_cmd) { | 
|  | case HOST_START_RES_CMD: | 
|  | version_res = (struct hbm_host_version_response *) mei_msg; | 
|  | if (version_res->host_version_supported) { | 
|  | dev->version.major_version = HBM_MAJOR_VERSION; | 
|  | dev->version.minor_version = HBM_MINOR_VERSION; | 
|  | if (dev->mei_state == MEI_INIT_CLIENTS && | 
|  | dev->init_clients_state == MEI_START_MESSAGE) { | 
|  | dev->init_clients_timer = 0; | 
|  | mei_host_enum_clients_message(dev); | 
|  | } else { | 
|  | dev->recvd_msg = false; | 
|  | dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); | 
|  | mei_reset(dev, 1); | 
|  | return; | 
|  | } | 
|  | } else { | 
|  | dev->version = version_res->me_max_version; | 
|  | /* send stop message */ | 
|  | mei_hdr = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; | 
|  | mei_hdr->host_addr = 0; | 
|  | mei_hdr->me_addr = 0; | 
|  | mei_hdr->length = sizeof(struct hbm_host_stop_request); | 
|  | mei_hdr->msg_complete = 1; | 
|  | mei_hdr->reserved = 0; | 
|  |  | 
|  | host_stop_req = (struct hbm_host_stop_request *) | 
|  | &dev->wr_msg_buf[1]; | 
|  |  | 
|  | memset(host_stop_req, | 
|  | 0, | 
|  | sizeof(struct hbm_host_stop_request)); | 
|  | host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; | 
|  | host_stop_req->reason = DRIVER_STOP_REQUEST; | 
|  | mei_write_message(dev, mei_hdr, | 
|  | (unsigned char *) (host_stop_req), | 
|  | mei_hdr->length); | 
|  | dev_dbg(&dev->pdev->dev, "version mismatch.\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dev->recvd_msg = true; | 
|  | dev_dbg(&dev->pdev->dev, "host start response message received.\n"); | 
|  | break; | 
|  |  | 
|  | case CLIENT_CONNECT_RES_CMD: | 
|  | connect_res = | 
|  | (struct hbm_client_connect_response *) mei_msg; | 
|  | mei_client_connect_response(dev, connect_res); | 
|  | dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); | 
|  | wake_up(&dev->wait_recvd_msg); | 
|  | break; | 
|  |  | 
|  | case CLIENT_DISCONNECT_RES_CMD: | 
|  | disconnect_res = | 
|  | (struct hbm_client_connect_response *) mei_msg; | 
|  | mei_client_disconnect_response(dev, disconnect_res); | 
|  | dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); | 
|  | wake_up(&dev->wait_recvd_msg); | 
|  | break; | 
|  |  | 
|  | case MEI_FLOW_CONTROL_CMD: | 
|  | flow_control = (struct hbm_flow_control *) mei_msg; | 
|  | mei_client_flow_control_response(dev, flow_control); | 
|  | dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); | 
|  | break; | 
|  |  | 
|  | case HOST_CLIENT_PROPERTIES_RES_CMD: | 
|  | props_res = (struct hbm_props_response *)mei_msg; | 
|  | if (props_res->status || !dev->me_clients) { | 
|  | dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); | 
|  | mei_reset(dev, 1); | 
|  | return; | 
|  | } | 
|  | if (dev->me_clients[dev->me_client_presentation_num] | 
|  | .client_id == props_res->address) { | 
|  |  | 
|  | dev->me_clients[dev->me_client_presentation_num].props | 
|  | = props_res->client_properties; | 
|  |  | 
|  | if (dev->mei_state == MEI_INIT_CLIENTS && | 
|  | dev->init_clients_state == | 
|  | MEI_CLIENT_PROPERTIES_MESSAGE) { | 
|  | dev->me_client_index++; | 
|  | dev->me_client_presentation_num++; | 
|  |  | 
|  | /** Send Client Properties request **/ | 
|  | res = mei_host_client_properties(dev); | 
|  | if (res < 0) { | 
|  | dev_dbg(&dev->pdev->dev, "mei_host_client_properties() failed"); | 
|  | return; | 
|  | } else if (!res) { | 
|  | /* | 
|  | * No more clients to send to. | 
|  | * Clear Map for indicating now ME clients | 
|  | * with associated host client | 
|  | */ | 
|  | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | 
|  | dev->open_handle_count = 0; | 
|  |  | 
|  | /* | 
|  | * Reserving the first three client IDs | 
|  | * Client Id 0 - Reserved for MEI Bus Message communications | 
|  | * Client Id 1 - Reserved for Watchdog | 
|  | * Client ID 2 - Reserved for AMTHI | 
|  | */ | 
|  | bitmap_set(dev->host_clients_map, 0, 3); | 
|  | dev->mei_state = MEI_ENABLED; | 
|  |  | 
|  | /* if wd initialization fails, initialization the AMTHI client, | 
|  | * otherwise the AMTHI client will be initialized after the WD client connect response | 
|  | * will be received | 
|  | */ | 
|  | if (mei_wd_host_init(dev)) | 
|  | mei_host_init_iamthif(dev); | 
|  | } | 
|  |  | 
|  | } else { | 
|  | dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message"); | 
|  | mei_reset(dev, 1); | 
|  | return; | 
|  | } | 
|  | } else { | 
|  | dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message for wrong client ID\n"); | 
|  | mei_reset(dev, 1); | 
|  | return; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case HOST_ENUM_RES_CMD: | 
|  | enum_res = (struct hbm_host_enum_response *) mei_msg; | 
|  | memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); | 
|  | if (dev->mei_state == MEI_INIT_CLIENTS && | 
|  | dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { | 
|  | dev->init_clients_timer = 0; | 
|  | dev->me_client_presentation_num = 0; | 
|  | dev->me_client_index = 0; | 
|  | mei_allocate_me_clients_storage(dev); | 
|  | dev->init_clients_state = | 
|  | MEI_CLIENT_PROPERTIES_MESSAGE; | 
|  | mei_host_client_properties(dev); | 
|  | } else { | 
|  | dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); | 
|  | mei_reset(dev, 1); | 
|  | return; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case HOST_STOP_RES_CMD: | 
|  | dev->mei_state = MEI_DISABLED; | 
|  | dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); | 
|  | mei_reset(dev, 1); | 
|  | break; | 
|  |  | 
|  | case CLIENT_DISCONNECT_REQ_CMD: | 
|  | /* search for client */ | 
|  | disconnect_req = | 
|  | (struct hbm_client_disconnect_request *) mei_msg; | 
|  | mei_client_disconnect_request(dev, disconnect_req); | 
|  | break; | 
|  |  | 
|  | case ME_STOP_REQ_CMD: | 
|  | /* prepare stop request */ | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; | 
|  | mei_hdr->host_addr = 0; | 
|  | mei_hdr->me_addr = 0; | 
|  | mei_hdr->length = sizeof(struct hbm_host_stop_request); | 
|  | mei_hdr->msg_complete = 1; | 
|  | mei_hdr->reserved = 0; | 
|  | host_stop_req = | 
|  | (struct hbm_host_stop_request *) &dev->ext_msg_buf[1]; | 
|  | memset(host_stop_req, 0, sizeof(struct hbm_host_stop_request)); | 
|  | host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; | 
|  | host_stop_req->reason = DRIVER_STOP_REQUEST; | 
|  | host_stop_req->reserved[0] = 0; | 
|  | host_stop_req->reserved[1] = 0; | 
|  | dev->extra_write_index = 2; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | BUG(); | 
|  | break; | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * _mei_hb_read - processes read related operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * @cb_pos: callback block. | 
|  | * @cl: private data of the file object. | 
|  | * @cmpl_list: complete list. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_read(struct mei_device *dev,	s32 *slots, | 
|  | struct mei_cl_cb *cb_pos, | 
|  | struct mei_cl *cl, | 
|  | struct mei_io_list *cmpl_list) | 
|  | { | 
|  | if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_flow_control))) { | 
|  | /* return the cancel routine */ | 
|  | list_del(&cb_pos->cb_list); | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_flow_control) + 3) / 4; | 
|  | if (mei_send_flow_control(dev, cl)) { | 
|  | cl->status = -ENODEV; | 
|  | cb_pos->information = 0; | 
|  | list_move_tail(&cb_pos->cb_list, &cmpl_list->mei_cb.cb_list); | 
|  | return -ENODEV; | 
|  | } | 
|  | list_move_tail(&cb_pos->cb_list, &dev->read_list.mei_cb.cb_list); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_ioctl - processes ioctl related operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * @cb_pos: callback block. | 
|  | * @cl: private data of the file object. | 
|  | * @cmpl_list: complete list. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, | 
|  | struct mei_cl_cb *cb_pos, | 
|  | struct mei_cl *cl, | 
|  | struct mei_io_list *cmpl_list) | 
|  | { | 
|  | if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_client_connect_request))) { | 
|  | cl->state = MEI_FILE_CONNECTING; | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | sizeof(struct hbm_client_connect_request) + 3) / 4; | 
|  | if (mei_connect(dev, cl)) { | 
|  | cl->status = -ENODEV; | 
|  | cb_pos->information = 0; | 
|  | list_del(&cb_pos->cb_list); | 
|  | return -ENODEV; | 
|  | } else { | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &dev->ctrl_rd_list.mei_cb.cb_list); | 
|  | cl->timer_count = MEI_CONNECT_TIMEOUT; | 
|  | } | 
|  | } else { | 
|  | /* return the cancel routine */ | 
|  | list_del(&cb_pos->cb_list); | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_cmpl - processes completed and no-iamthif operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * @cb_pos: callback block. | 
|  | * @cl: private data of the file object. | 
|  | * @cmpl_list: complete list. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_cmpl(struct mei_device *dev,	s32 *slots, | 
|  | struct mei_cl_cb *cb_pos, | 
|  | struct mei_cl *cl, | 
|  | struct mei_io_list *cmpl_list) | 
|  | { | 
|  | struct mei_msg_hdr *mei_hdr; | 
|  |  | 
|  | if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + | 
|  | (cb_pos->request_buffer.size - | 
|  | cb_pos->information))) { | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | 
|  | mei_hdr->host_addr = cl->host_client_id; | 
|  | mei_hdr->me_addr = cl->me_client_id; | 
|  | mei_hdr->length = cb_pos->request_buffer.size - | 
|  | cb_pos->information; | 
|  | mei_hdr->msg_complete = 1; | 
|  | mei_hdr->reserved = 0; | 
|  | dev_dbg(&dev->pdev->dev, "cb_pos->request_buffer.size =%d" | 
|  | "mei_hdr->msg_complete = %d\n", | 
|  | cb_pos->request_buffer.size, | 
|  | mei_hdr->msg_complete); | 
|  | dev_dbg(&dev->pdev->dev, "cb_pos->information  =%lu\n", | 
|  | cb_pos->information); | 
|  | dev_dbg(&dev->pdev->dev, "mei_hdr->length  =%d\n", | 
|  | mei_hdr->length); | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | mei_hdr->length + 3) / 4; | 
|  | if (mei_write_message(dev, mei_hdr, | 
|  | (unsigned char *) | 
|  | (cb_pos->request_buffer.data + | 
|  | cb_pos->information), | 
|  | mei_hdr->length)) { | 
|  | cl->status = -ENODEV; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &cmpl_list->mei_cb.cb_list); | 
|  | return -ENODEV; | 
|  | } else { | 
|  | if (mei_flow_ctrl_reduce(dev, cl)) | 
|  | return -ENODEV; | 
|  | cl->status = 0; | 
|  | cb_pos->information += mei_hdr->length; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &dev->write_waiting_list.mei_cb.cb_list); | 
|  | } | 
|  | } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { | 
|  | /* buffer is still empty */ | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | 
|  | mei_hdr->host_addr = cl->host_client_id; | 
|  | mei_hdr->me_addr = cl->me_client_id; | 
|  | mei_hdr->length = | 
|  | (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); | 
|  | mei_hdr->msg_complete = 0; | 
|  | mei_hdr->reserved = 0; | 
|  |  | 
|  | (*slots) -= (sizeof(struct mei_msg_hdr) + | 
|  | mei_hdr->length + 3) / 4; | 
|  | if (mei_write_message(dev, mei_hdr, | 
|  | (unsigned char *) | 
|  | (cb_pos->request_buffer.data + | 
|  | cb_pos->information), | 
|  | mei_hdr->length)) { | 
|  | cl->status = -ENODEV; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &cmpl_list->mei_cb.cb_list); | 
|  | return -ENODEV; | 
|  | } else { | 
|  | cb_pos->information += mei_hdr->length; | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "cb_pos->request_buffer.size =%d" | 
|  | " mei_hdr->msg_complete = %d\n", | 
|  | cb_pos->request_buffer.size, | 
|  | mei_hdr->msg_complete); | 
|  | dev_dbg(&dev->pdev->dev, "cb_pos->information  =%lu\n", | 
|  | cb_pos->information); | 
|  | dev_dbg(&dev->pdev->dev, "mei_hdr->length  =%d\n", | 
|  | mei_hdr->length); | 
|  | } | 
|  | return -EMSGSIZE; | 
|  | } else { | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _mei_irq_thread_cmpl_iamthif - processes completed iamthif operation. | 
|  | * | 
|  | * @dev: the device structure. | 
|  | * @slots: free slots. | 
|  | * @cb_pos: callback block. | 
|  | * @cl: private data of the file object. | 
|  | * @cmpl_list: complete list. | 
|  | * | 
|  | * returns 0, OK; otherwise, error. | 
|  | */ | 
|  | static int _mei_irq_thread_cmpl_iamthif(struct mei_device *dev, s32 *slots, | 
|  | struct mei_cl_cb *cb_pos, | 
|  | struct mei_cl *cl, | 
|  | struct mei_io_list *cmpl_list) | 
|  | { | 
|  | struct mei_msg_hdr *mei_hdr; | 
|  |  | 
|  | if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + | 
|  | dev->iamthif_msg_buf_size - | 
|  | dev->iamthif_msg_buf_index)) { | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | 
|  | mei_hdr->host_addr = cl->host_client_id; | 
|  | mei_hdr->me_addr = cl->me_client_id; | 
|  | mei_hdr->length = dev->iamthif_msg_buf_size - | 
|  | dev->iamthif_msg_buf_index; | 
|  | mei_hdr->msg_complete = 1; | 
|  | mei_hdr->reserved = 0; | 
|  |  | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | mei_hdr->length + 3) / 4; | 
|  |  | 
|  | if (mei_write_message(dev, mei_hdr, | 
|  | (dev->iamthif_msg_buf + | 
|  | dev->iamthif_msg_buf_index), | 
|  | mei_hdr->length)) { | 
|  | dev->iamthif_state = MEI_IAMTHIF_IDLE; | 
|  | cl->status = -ENODEV; | 
|  | list_del(&cb_pos->cb_list); | 
|  | return -ENODEV; | 
|  | } else { | 
|  | if (mei_flow_ctrl_reduce(dev, cl)) | 
|  | return -ENODEV; | 
|  | dev->iamthif_msg_buf_index += mei_hdr->length; | 
|  | cb_pos->information = dev->iamthif_msg_buf_index; | 
|  | cl->status = 0; | 
|  | dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; | 
|  | dev->iamthif_flow_control_pending = true; | 
|  | /* save iamthif cb sent to amthi client */ | 
|  | dev->iamthif_current_cb = cb_pos; | 
|  | list_move_tail(&cb_pos->cb_list, | 
|  | &dev->write_waiting_list.mei_cb.cb_list); | 
|  |  | 
|  | } | 
|  | } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { | 
|  | /* buffer is still empty */ | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | 
|  | mei_hdr->host_addr = cl->host_client_id; | 
|  | mei_hdr->me_addr = cl->me_client_id; | 
|  | mei_hdr->length = | 
|  | (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); | 
|  | mei_hdr->msg_complete = 0; | 
|  | mei_hdr->reserved = 0; | 
|  |  | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | mei_hdr->length + 3) / 4; | 
|  |  | 
|  | if (mei_write_message(dev, mei_hdr, | 
|  | (dev->iamthif_msg_buf + | 
|  | dev->iamthif_msg_buf_index), | 
|  | mei_hdr->length)) { | 
|  | cl->status = -ENODEV; | 
|  | list_del(&cb_pos->cb_list); | 
|  | } else { | 
|  | dev->iamthif_msg_buf_index += mei_hdr->length; | 
|  | } | 
|  | return -EMSGSIZE; | 
|  | } else { | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * mei_irq_thread_read_handler - bottom half read routine after ISR to | 
|  | * handle the read processing. | 
|  | * | 
|  | * @cmpl_list: An instance of our list structure | 
|  | * @dev: the device structure | 
|  | * @slots: slots to read. | 
|  | * | 
|  | * returns 0 on success, <0 on failure. | 
|  | */ | 
|  | static int mei_irq_thread_read_handler(struct mei_io_list *cmpl_list, | 
|  | struct mei_device *dev, | 
|  | s32 *slots) | 
|  | { | 
|  | struct mei_msg_hdr *mei_hdr; | 
|  | struct mei_cl *cl_pos = NULL; | 
|  | struct mei_cl *cl_next = NULL; | 
|  | int ret = 0; | 
|  |  | 
|  | if (!dev->rd_msg_hdr) { | 
|  | dev->rd_msg_hdr = mei_mecbrw_read(dev); | 
|  | dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); | 
|  | (*slots)--; | 
|  | dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); | 
|  | } | 
|  | mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr; | 
|  | dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); | 
|  |  | 
|  | if (mei_hdr->reserved || !dev->rd_msg_hdr) { | 
|  | dev_dbg(&dev->pdev->dev, "corrupted message header.\n"); | 
|  | ret = -EBADMSG; | 
|  | goto end; | 
|  | } | 
|  |  | 
|  | if (mei_hdr->host_addr || mei_hdr->me_addr) { | 
|  | list_for_each_entry_safe(cl_pos, cl_next, | 
|  | &dev->file_list, link) { | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "list_for_each_entry_safe read host" | 
|  | " client = %d, ME client = %d\n", | 
|  | cl_pos->host_client_id, | 
|  | cl_pos->me_client_id); | 
|  | if (cl_pos->host_client_id == mei_hdr->host_addr && | 
|  | cl_pos->me_client_id == mei_hdr->me_addr) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (&cl_pos->link == &dev->file_list) { | 
|  | dev_dbg(&dev->pdev->dev, "corrupted message header\n"); | 
|  | ret = -EBADMSG; | 
|  | goto end; | 
|  | } | 
|  | } | 
|  | if (((*slots) * sizeof(u32)) < mei_hdr->length) { | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "we can't read the message slots =%08x.\n", | 
|  | *slots); | 
|  | /* we can't read the message */ | 
|  | ret = -ERANGE; | 
|  | goto end; | 
|  | } | 
|  |  | 
|  | /* decide where to read the message too */ | 
|  | if (!mei_hdr->host_addr) { | 
|  | dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_bus_message.\n"); | 
|  | mei_irq_thread_read_bus_message(dev, mei_hdr); | 
|  | dev_dbg(&dev->pdev->dev, "end mei_irq_thread_read_bus_message.\n"); | 
|  | } else if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && | 
|  | (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && | 
|  | (dev->iamthif_state == MEI_IAMTHIF_READING)) { | 
|  | dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); | 
|  | dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", | 
|  | mei_hdr->length); | 
|  | ret = mei_irq_thread_read_amthi_message(cmpl_list, | 
|  | dev, mei_hdr); | 
|  | if (ret) | 
|  | goto end; | 
|  |  | 
|  | } else { | 
|  | dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_client_message.\n"); | 
|  | ret = mei_irq_thread_read_client_message(cmpl_list, | 
|  | dev, mei_hdr); | 
|  | if (ret) | 
|  | goto end; | 
|  |  | 
|  | } | 
|  |  | 
|  | /* reset the number of slots and header */ | 
|  | *slots = mei_count_full_read_slots(dev); | 
|  | dev->rd_msg_hdr = 0; | 
|  |  | 
|  | if (*slots == -EOVERFLOW) { | 
|  | /* overflow - reset */ | 
|  | dev_dbg(&dev->pdev->dev, "resetting due to slots overflow.\n"); | 
|  | /* set the event since message has been read */ | 
|  | ret = -ERANGE; | 
|  | goto end; | 
|  | } | 
|  | end: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * mei_irq_thread_write_handler - bottom half write routine after | 
|  | * ISR to handle the write processing. | 
|  | * | 
|  | * @cmpl_list: An instance of our list structure | 
|  | * @dev: the device structure | 
|  | * @slots: slots to write. | 
|  | * | 
|  | * returns 0 on success, <0 on failure. | 
|  | */ | 
|  | static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, | 
|  | struct mei_device *dev, | 
|  | s32 *slots) | 
|  | { | 
|  |  | 
|  | struct mei_cl *cl; | 
|  | struct mei_cl_cb *pos = NULL, *next = NULL; | 
|  | struct mei_io_list *list; | 
|  | int ret; | 
|  |  | 
|  | if (!mei_host_buffer_is_empty(dev)) { | 
|  | dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n"); | 
|  | return 0; | 
|  | } | 
|  | *slots = mei_count_empty_write_slots(dev); | 
|  | /* complete all waiting for write CB */ | 
|  | dev_dbg(&dev->pdev->dev, "complete all waiting for write cb.\n"); | 
|  |  | 
|  | list = &dev->write_waiting_list; | 
|  | list_for_each_entry_safe(pos, next, | 
|  | &list->mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *)pos->file_private; | 
|  | if (cl == NULL) | 
|  | continue; | 
|  |  | 
|  | cl->status = 0; | 
|  | list_del(&pos->cb_list); | 
|  | if (MEI_WRITING == cl->writing_state && | 
|  | (pos->major_file_operations == MEI_WRITE) && | 
|  | (cl != &dev->iamthif_cl)) { | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "MEI WRITE COMPLETE\n"); | 
|  | cl->writing_state = MEI_WRITE_COMPLETE; | 
|  | list_add_tail(&pos->cb_list, | 
|  | &cmpl_list->mei_cb.cb_list); | 
|  | } | 
|  | if (cl == &dev->iamthif_cl) { | 
|  | dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n"); | 
|  | if (dev->iamthif_flow_control_pending) { | 
|  | ret = _mei_irq_thread_iamthif_read( | 
|  | dev, slots); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (dev->stop && !dev->wd_pending) { | 
|  | dev->wd_stopped = true; | 
|  | wake_up_interruptible(&dev->wait_stop_wd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (dev->extra_write_index) { | 
|  | dev_dbg(&dev->pdev->dev, "extra_write_index =%d.\n", | 
|  | dev->extra_write_index); | 
|  | mei_write_message(dev, | 
|  | (struct mei_msg_hdr *) &dev->ext_msg_buf[0], | 
|  | (unsigned char *) &dev->ext_msg_buf[1], | 
|  | (dev->extra_write_index - 1) * sizeof(u32)); | 
|  | *slots -= dev->extra_write_index; | 
|  | dev->extra_write_index = 0; | 
|  | } | 
|  | if (dev->mei_state == MEI_ENABLED) { | 
|  | if (dev->wd_pending && | 
|  | mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { | 
|  | if (mei_wd_send(dev)) | 
|  | dev_dbg(&dev->pdev->dev, "wd send failed.\n"); | 
|  | else | 
|  | if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) | 
|  | return -ENODEV; | 
|  |  | 
|  | dev->wd_pending = false; | 
|  |  | 
|  | if (dev->wd_timeout) { | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | MEI_START_WD_DATA_SIZE + 3) / 4; | 
|  | dev->wd_due_counter = 2; | 
|  | } else { | 
|  | *slots -= (sizeof(struct mei_msg_hdr) + | 
|  | MEI_WD_PARAMS_SIZE + 3) / 4; | 
|  | dev->wd_due_counter = 0; | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  | if (dev->stop) | 
|  | return -ENODEV; | 
|  |  | 
|  | /* complete control write list CB */ | 
|  | dev_dbg(&dev->pdev->dev, "complete control write list cb.\n"); | 
|  | list_for_each_entry_safe(pos, next, | 
|  | &dev->ctrl_wr_list.mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *) pos->file_private; | 
|  | if (!cl) { | 
|  | list_del(&pos->cb_list); | 
|  | return -ENODEV; | 
|  | } | 
|  | switch (pos->major_file_operations) { | 
|  | case MEI_CLOSE: | 
|  | /* send disconnect message */ | 
|  | ret = _mei_irq_thread_close(dev, slots, pos, cl, cmpl_list); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | break; | 
|  | case MEI_READ: | 
|  | /* send flow control message */ | 
|  | ret = _mei_irq_thread_read(dev, slots, pos, cl, cmpl_list); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | break; | 
|  | case MEI_IOCTL: | 
|  | /* connect message */ | 
|  | if (mei_other_client_is_connecting(dev, cl)) | 
|  | continue; | 
|  | ret = _mei_irq_thread_ioctl(dev, slots, pos, cl, cmpl_list); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | BUG(); | 
|  | } | 
|  |  | 
|  | } | 
|  | /* complete  write list CB */ | 
|  | dev_dbg(&dev->pdev->dev, "complete write list cb.\n"); | 
|  | list_for_each_entry_safe(pos, next, | 
|  | &dev->write_list.mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *)pos->file_private; | 
|  | if (cl == NULL) | 
|  | continue; | 
|  |  | 
|  | if (cl != &dev->iamthif_cl) { | 
|  | if (!mei_flow_ctrl_creds(dev, cl)) { | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "No flow control" | 
|  | " credentials for client" | 
|  | " %d, not sending.\n", | 
|  | cl->host_client_id); | 
|  | continue; | 
|  | } | 
|  | ret = _mei_irq_thread_cmpl(dev, slots, | 
|  | pos, | 
|  | cl, cmpl_list); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | } else if (cl == &dev->iamthif_cl) { | 
|  | /* IAMTHIF IOCTL */ | 
|  | dev_dbg(&dev->pdev->dev, "complete amthi write cb.\n"); | 
|  | if (!mei_flow_ctrl_creds(dev, cl)) { | 
|  | dev_dbg(&dev->pdev->dev, | 
|  | "No flow control" | 
|  | " credentials for amthi" | 
|  | " client %d.\n", | 
|  | cl->host_client_id); | 
|  | continue; | 
|  | } | 
|  | ret = _mei_irq_thread_cmpl_iamthif(dev, | 
|  | slots, | 
|  | pos, | 
|  | cl, | 
|  | cmpl_list); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | } | 
|  |  | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /** | 
|  | * mei_timer - timer function. | 
|  | * | 
|  | * @work: pointer to the work_struct structure | 
|  | * | 
|  | * NOTE: This function is called by timer interrupt work | 
|  | */ | 
|  | void mei_timer(struct work_struct *work) | 
|  | { | 
|  | unsigned long timeout; | 
|  | struct mei_cl *cl_pos = NULL; | 
|  | struct mei_cl *cl_next = NULL; | 
|  | struct list_head *amthi_complete_list = NULL; | 
|  | struct mei_cl_cb  *cb_pos = NULL; | 
|  | struct mei_cl_cb  *cb_next = NULL; | 
|  |  | 
|  | struct mei_device *dev = container_of(work, | 
|  | struct mei_device, timer_work.work); | 
|  |  | 
|  |  | 
|  | mutex_lock(&dev->device_lock); | 
|  | if (dev->mei_state != MEI_ENABLED) { | 
|  | if (dev->mei_state == MEI_INIT_CLIENTS) { | 
|  | if (dev->init_clients_timer) { | 
|  | if (--dev->init_clients_timer == 0) { | 
|  | dev_dbg(&dev->pdev->dev, "IMEI reset due to init clients timeout ,init clients state = %d.\n", | 
|  | dev->init_clients_state); | 
|  | mei_reset(dev, 1); | 
|  | } | 
|  | } | 
|  | } | 
|  | goto out; | 
|  | } | 
|  | /*** connect/disconnect timeouts ***/ | 
|  | list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { | 
|  | if (cl_pos->timer_count) { | 
|  | if (--cl_pos->timer_count == 0) { | 
|  | dev_dbg(&dev->pdev->dev, "HECI reset due to connect/disconnect timeout.\n"); | 
|  | mei_reset(dev, 1); | 
|  | goto out; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (dev->iamthif_stall_timer) { | 
|  | if (--dev->iamthif_stall_timer == 0) { | 
|  | dev_dbg(&dev->pdev->dev, "resetting because of hang to amthi.\n"); | 
|  | mei_reset(dev, 1); | 
|  | dev->iamthif_msg_buf_size = 0; | 
|  | dev->iamthif_msg_buf_index = 0; | 
|  | dev->iamthif_canceled = false; | 
|  | dev->iamthif_ioctl = true; | 
|  | dev->iamthif_state = MEI_IAMTHIF_IDLE; | 
|  | dev->iamthif_timer = 0; | 
|  |  | 
|  | if (dev->iamthif_current_cb) | 
|  | mei_free_cb_private(dev->iamthif_current_cb); | 
|  |  | 
|  | dev->iamthif_file_object = NULL; | 
|  | dev->iamthif_current_cb = NULL; | 
|  | mei_run_next_iamthif_cmd(dev); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (dev->iamthif_timer) { | 
|  |  | 
|  | timeout = dev->iamthif_timer + | 
|  | msecs_to_jiffies(IAMTHIF_READ_TIMER); | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", | 
|  | dev->iamthif_timer); | 
|  | dev_dbg(&dev->pdev->dev, "timeout = %ld\n", timeout); | 
|  | dev_dbg(&dev->pdev->dev, "jiffies = %ld\n", jiffies); | 
|  | if (time_after(jiffies, timeout)) { | 
|  | /* | 
|  | * User didn't read the AMTHI data on time (15sec) | 
|  | * freeing AMTHI for other requests | 
|  | */ | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "freeing AMTHI for other requests\n"); | 
|  |  | 
|  | amthi_complete_list = &dev->amthi_read_complete_list. | 
|  | mei_cb.cb_list; | 
|  |  | 
|  | list_for_each_entry_safe(cb_pos, cb_next, amthi_complete_list, cb_list) { | 
|  |  | 
|  | cl_pos = cb_pos->file_object->private_data; | 
|  |  | 
|  | /* Finding the AMTHI entry. */ | 
|  | if (cl_pos == &dev->iamthif_cl) | 
|  | list_del(&cb_pos->cb_list); | 
|  | } | 
|  | if (dev->iamthif_current_cb) | 
|  | mei_free_cb_private(dev->iamthif_current_cb); | 
|  |  | 
|  | dev->iamthif_file_object->private_data = NULL; | 
|  | dev->iamthif_file_object = NULL; | 
|  | dev->iamthif_current_cb = NULL; | 
|  | dev->iamthif_timer = 0; | 
|  | mei_run_next_iamthif_cmd(dev); | 
|  |  | 
|  | } | 
|  | } | 
|  | out: | 
|  | schedule_delayed_work(&dev->timer_work, 2 * HZ); | 
|  | mutex_unlock(&dev->device_lock); | 
|  | } | 
|  |  | 
|  | /** | 
|  | *  mei_interrupt_thread_handler - function called after ISR to handle the interrupt | 
|  | * processing. | 
|  | * | 
|  | * @irq: The irq number | 
|  | * @dev_id: pointer to the device structure | 
|  | * | 
|  | * returns irqreturn_t | 
|  | * | 
|  | */ | 
|  | irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) | 
|  | { | 
|  | struct mei_device *dev = (struct mei_device *) dev_id; | 
|  | struct mei_io_list complete_list; | 
|  | struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; | 
|  | struct mei_cl *cl; | 
|  | s32 slots; | 
|  | int rets; | 
|  | bool  bus_message_received; | 
|  |  | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); | 
|  | /* initialize our complete list */ | 
|  | mutex_lock(&dev->device_lock); | 
|  | mei_io_list_init(&complete_list); | 
|  | dev->host_hw_state = mei_hcsr_read(dev); | 
|  |  | 
|  | /* Ack the interrupt here | 
|  | * In case of MSI we don't go through the quick handler */ | 
|  | if (pci_dev_msi_enabled(dev->pdev)) | 
|  | mei_reg_write(dev, H_CSR, dev->host_hw_state); | 
|  |  | 
|  | dev->me_hw_state = mei_mecsr_read(dev); | 
|  |  | 
|  | /* check if ME wants a reset */ | 
|  | if ((dev->me_hw_state & ME_RDY_HRA) == 0 && | 
|  | dev->mei_state != MEI_RESETING && | 
|  | dev->mei_state != MEI_INITIALIZING) { | 
|  | dev_dbg(&dev->pdev->dev, "FW not ready.\n"); | 
|  | mei_reset(dev, 1); | 
|  | mutex_unlock(&dev->device_lock); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | /*  check if we need to start the dev */ | 
|  | if ((dev->host_hw_state & H_RDY) == 0) { | 
|  | if ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA) { | 
|  | dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); | 
|  | dev->host_hw_state |= (H_IE | H_IG | H_RDY); | 
|  | mei_hcsr_set(dev); | 
|  | dev->mei_state = MEI_INIT_CLIENTS; | 
|  | dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); | 
|  | /* link is established | 
|  | * start sending messages. | 
|  | */ | 
|  | mei_host_start_message(dev); | 
|  | mutex_unlock(&dev->device_lock); | 
|  | return IRQ_HANDLED; | 
|  | } else { | 
|  | dev_dbg(&dev->pdev->dev, "FW not ready.\n"); | 
|  | mutex_unlock(&dev->device_lock); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  | } | 
|  | /* check slots available for reading */ | 
|  | slots = mei_count_full_read_slots(dev); | 
|  | dev_dbg(&dev->pdev->dev, "slots =%08x  extra_write_index =%08x.\n", | 
|  | slots, dev->extra_write_index); | 
|  | while (slots > 0 && !dev->extra_write_index) { | 
|  | dev_dbg(&dev->pdev->dev, "slots =%08x  extra_write_index =%08x.\n", | 
|  | slots, dev->extra_write_index); | 
|  | dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_handler.\n"); | 
|  | rets = mei_irq_thread_read_handler(&complete_list, dev, &slots); | 
|  | if (rets) | 
|  | goto end; | 
|  | } | 
|  | rets = mei_irq_thread_write_handler(&complete_list, dev, &slots); | 
|  | end: | 
|  | dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); | 
|  | dev->host_hw_state = mei_hcsr_read(dev); | 
|  | dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); | 
|  |  | 
|  | bus_message_received = false; | 
|  | if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { | 
|  | dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); | 
|  | bus_message_received = true; | 
|  | } | 
|  | mutex_unlock(&dev->device_lock); | 
|  | if (bus_message_received) { | 
|  | dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); | 
|  | wake_up_interruptible(&dev->wait_recvd_msg); | 
|  | bus_message_received = false; | 
|  | } | 
|  | if (list_empty(&complete_list.mei_cb.cb_list)) | 
|  | return IRQ_HANDLED; | 
|  |  | 
|  |  | 
|  | list_for_each_entry_safe(cb_pos, cb_next, | 
|  | &complete_list.mei_cb.cb_list, cb_list) { | 
|  | cl = (struct mei_cl *)cb_pos->file_private; | 
|  | list_del(&cb_pos->cb_list); | 
|  | if (cl) { | 
|  | if (cl != &dev->iamthif_cl) { | 
|  | dev_dbg(&dev->pdev->dev, "completing call back.\n"); | 
|  | _mei_cmpl(cl, cb_pos); | 
|  | cb_pos = NULL; | 
|  | } else if (cl == &dev->iamthif_cl) { | 
|  | _mei_cmpl_iamthif(dev, cb_pos); | 
|  | } | 
|  | } | 
|  | } | 
|  | return IRQ_HANDLED; | 
|  | } |