/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Part of Intel(R) Manageability Engine Interface Linux driver * * Copyright (c) 2003 - 2008 Intel Corp. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * 3. Neither the names of the above-listed copyright holders nor the names * of any contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "heci_data_structures.h" #include "heci.h" #include "heci_interface.h" /* * interrupt function prototypes */ static void heci_bh_handler(void *data); static int heci_bh_read_handler(struct io_heci_list *complete_list, struct iamt_heci_device *dev, int32_t *slots); static int heci_bh_write_handler(struct io_heci_list *complete_list, struct iamt_heci_device *dev, int32_t *slots); static void heci_bh_read_bus_message(struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr); static int heci_bh_read_pthi_message(struct io_heci_list *complete_list, struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr); static int heci_bh_read_client_message(struct io_heci_list *complete_list, struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr); static void heci_client_connect_response(struct iamt_heci_device *dev, struct hbm_client_connect_response *connect_res); static void heci_client_disconnect_response(struct iamt_heci_device *dev, struct hbm_client_connect_response *disconnect_res); static void heci_client_flow_control_response(struct iamt_heci_device *dev, struct hbm_flow_control *flow_control); static void heci_client_disconnect_request(struct iamt_heci_device *dev, struct hbm_client_disconnect_request *disconnect_req); static int heci_bh_process_device(struct iamt_heci_device *dev); /* * heci_isr_interrupt - The ISR of the HECI device * * @irq: The irq number * @dev_id: pointer to the device structure * * @return irqreturn_t */ uint_t heci_isr_interrupt(caddr_t arg1) { struct iamt_heci_device *device = (struct iamt_heci_device *)(void *)arg1; mutex_enter(&device->device_lock); if (device->heci_state == HECI_POWER_DOWN) { mutex_exit(&device->device_lock); return (DDI_INTR_UNCLAIMED); } device->host_hw_state = read_heci_register(device, H_CSR); if ((device->host_hw_state & H_IS) != H_IS) { mutex_exit(&device->device_lock); return (DDI_INTR_UNCLAIMED); } /* disable interrupts */ heci_csr_disable_interrupts(device); mutex_exit(&device->device_lock); /* * Our device interrupted, schedule work the heci_bh_handler * to handle the interrupt processing. This needs to be a * taskq */ DBG("schedule work the heci_bh_handler.\n"); if (ddi_taskq_dispatch(device->work, heci_bh_handler, (void*)arg1, DDI_NOSLEEP) == DDI_FAILURE) cmn_err(CE_WARN, "taskq_dispatch failed for heci_bh_handler"); return (DDI_INTR_CLAIMED); } /* * _heci_cmpl: process completed operation. * * @file_ext: private data of the file object. * @priv_cb_pos: callback block. */ static void _heci_cmpl(struct heci_file_private *file_ext, struct heci_cb_private *priv_cb_pos) { if (priv_cb_pos->major_file_operations == HECI_WRITE) { heci_free_cb_private(priv_cb_pos); DBG("completing write call back.\n"); file_ext->writing_state = HECI_WRITE_COMPLETE; pollwakeup(&file_ext->tx_pollwait, POLL_IN|POLLRDNORM); } else if (priv_cb_pos->major_file_operations == HECI_READ && HECI_READING == file_ext->reading_state) { DBG("completing read call back information= %lu\n", priv_cb_pos->information); file_ext->reading_state = HECI_READ_COMPLETE; cv_broadcast(&file_ext->rx_wait); } } /* * _heci_cmpl_iamthif: process completed iamthif operation. * * @dev: Device object for our driver. * @priv_cb_pos: callback block. */ static void _heci_cmpl_iamthif(struct iamt_heci_device *dev, struct heci_cb_private *priv_cb_pos) { if (dev->iamthif_canceled != 1) { dev->iamthif_state = HECI_IAMTHIF_READ_COMPLETE; dev->iamthif_stall_timer = 0; (void) memcpy(priv_cb_pos->response_buffer.data, dev->iamthif_msg_buf, dev->iamthif_msg_buf_index); list_add_tail(&priv_cb_pos->cb_list, &dev->pthi_read_complete_list.heci_cb.cb_list); DBG("pthi read completed.\n"); } else { run_next_iamthif_cmd(dev); } if (&dev->iamthif_file_ext.pollwait) { DBG("completing pthi call back.\n"); pollwakeup(&dev->iamthif_file_ext.pollwait, POLL_IN|POLLRDNORM); } } /* * heci_bh_handler - function called after ISR to handle the interrupt * processing. * * @work: pointer to the work structure * * NOTE: This function is called by schedule work */ static void heci_bh_handler(void *data) { struct iamt_heci_device *dev = (struct iamt_heci_device *)data; #ifdef SUNOS while (heci_bh_process_device(dev)) ; #elif defined(LINUX) if (heci_bh_process_device(dev)) { PEPARE_WORK(&dev->work, heci_bh_handler); DBG("schedule work the heci_bh_handler.\n"); rets = schedule_work(&dev->work); if (!rets) { printk(KERN_ERR "heci: schedule the heci_bh_handler" " failed error=%x\n", rets); } } #else #error "Unknown platform!" #endif } static int heci_bh_process_device(struct iamt_heci_device *dev) { struct io_heci_list complete_list; int32_t slots; int rets, isr_pending = 0; struct heci_cb_private *cb_pos = NULL, *cb_next = NULL; struct heci_file_private *file_ext; int bus_message_received = 0; DBG("function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ mutex_enter(&dev->device_lock); heci_initialize_list(&complete_list, dev); dev->host_hw_state = read_heci_register(dev, H_CSR); dev->me_hw_state = read_heci_register(dev, ME_CSR_HA); /* check if ME wants a reset */ if (((dev->me_hw_state & ME_RDY_HRA) == 0) && (dev->heci_state != HECI_RESETING) && (dev->heci_state != HECI_INITIALIZING)) { DBG("FW not ready.\n"); heci_reset(dev, 1); mutex_exit(&dev->device_lock); return (0); } /* 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) { DBG("we need to start the dev.\n"); dev->host_hw_state |= (H_IE | H_IG | H_RDY); heci_set_csr_register(dev); if (dev->heci_state == HECI_INITIALIZING) { dev->recvd_msg = 1; cv_broadcast(&dev->wait_recvd_msg); mutex_exit(&dev->device_lock); return (0); } else { mutex_exit(&dev->device_lock); if (dev->reinit_tsk && ddi_taskq_dispatch(dev->reinit_tsk, heci_task_initialize_clients, dev, DDI_SLEEP) == DDI_FAILURE) { cmn_err(CE_WARN, "taskq_dispatch " "failed for reinit_tsk"); } return (0); } } else { DBG("enable interrupt FW not ready.\n"); heci_csr_enable_interrupts(dev); mutex_exit(&dev->device_lock); return (0); } } /* check slots avalable for reading */ slots = count_full_read_slots(dev); DBG("slots =%08x extra_write_index =%08x.\n", slots, dev->extra_write_index); while ((slots > 0) && (!dev->extra_write_index)) { DBG("slots =%08x extra_write_index =%08x.\n", slots, dev->extra_write_index); DBG("call heci_bh_read_handler.\n"); rets = heci_bh_read_handler(&complete_list, dev, &slots); if (rets != 0) goto end; } rets = heci_bh_write_handler(&complete_list, dev, &slots); end: DBG("end of bottom half function.\n"); dev->host_hw_state = read_heci_register(dev, H_CSR); dev->host_buffer_is_empty = host_buffer_is_empty(dev); if ((dev->host_hw_state & H_IS) == H_IS) { /* acknowledge interrupt and disable interrupts */ heci_csr_disable_interrupts(dev); DBG("schedule work the heci_bh_handler.\n"); isr_pending = 1; } else { heci_csr_enable_interrupts(dev); } if (dev->recvd_msg) { DBG("received waiting bus message\n"); bus_message_received = 1; } if (bus_message_received) { DBG("wake up dev->wait_recvd_msg\n"); cv_broadcast(&dev->wait_recvd_msg); bus_message_received = 0; } if ((complete_list.status != 0) || list_empty(&complete_list.heci_cb.cb_list)) { mutex_exit(&dev->device_lock); return (isr_pending); } mutex_exit(&dev->device_lock); list_for_each_entry_safe(cb_pos, cb_next, &complete_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *)cb_pos->file_private; list_del(&cb_pos->cb_list); if (file_ext != NULL) { if (file_ext != &dev->iamthif_file_ext) { DBG("completing call back.\n"); _heci_cmpl(file_ext, cb_pos); cb_pos = NULL; } else if (file_ext == &dev->iamthif_file_ext) { _heci_cmpl_iamthif(dev, cb_pos); } } } return (isr_pending); } /* * heci_bh_read_handler - bottom half read routine after ISR to * handle the read processing. * * @cmpl_list: An instance of our list structure * @dev: Device object for our driver * @slots: slots to read. * * @return 0 on success, <0 on failure. */ static int heci_bh_read_handler(struct io_heci_list *cmpl_list, struct iamt_heci_device *dev, int32_t *slots) { struct heci_msg_hdr *heci_hdr; int ret = 0; struct heci_file_private *file_pos = NULL; struct heci_file_private *file_next = NULL; if (!dev->rd_msg_hdr) { dev->rd_msg_hdr = read_heci_register(dev, ME_CB_RW); DBG("slots=%08x.\n", *slots); (*slots)--; DBG("slots=%08x.\n", *slots); } heci_hdr = (struct heci_msg_hdr *)&dev->rd_msg_hdr; DBG("heci_hdr->length =%d\n", heci_hdr->length); if ((heci_hdr->reserved) || !(dev->rd_msg_hdr)) { DBG("corrupted message header.\n"); ret = -ECORRUPTED_MESSAGE_HEADER; goto end; } if ((heci_hdr->host_addr) || (heci_hdr->me_addr)) { list_for_each_entry_safe(file_pos, file_next, &dev->file_list, link, struct heci_file_private) { DBG("list_for_each_entry_safe read host" " client = %d, ME client = %d\n", file_pos->host_client_id, file_pos->me_client_id); if ((file_pos->host_client_id == heci_hdr->host_addr) && (file_pos->me_client_id == heci_hdr->me_addr)) break; } if (&file_pos->link == &dev->file_list) { DBG("corrupted message header\n"); ret = -ECORRUPTED_MESSAGE_HEADER; goto end; } } if (((*slots) * sizeof (uint32_t)) < heci_hdr->length) { DBG("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 (!heci_hdr->host_addr) { DBG("call heci_bh_read_bus_message.\n"); heci_bh_read_bus_message(dev, heci_hdr); DBG("end heci_bh_read_bus_message.\n"); } else if ((heci_hdr->host_addr == dev->iamthif_file_ext.host_client_id) && (HECI_FILE_CONNECTED == dev->iamthif_file_ext.state) && (dev->iamthif_state == HECI_IAMTHIF_READING)) { DBG("call heci_bh_read_iamthif_message.\n"); DBG("heci_hdr->length =%d\n", heci_hdr->length); ret = heci_bh_read_pthi_message(cmpl_list, dev, heci_hdr); if (ret != 0) goto end; } else { DBG("call heci_bh_read_client_message.\n"); ret = heci_bh_read_client_message(cmpl_list, dev, heci_hdr); if (ret != 0) goto end; } /* reset the number of slots and header */ *slots = count_full_read_slots(dev); dev->rd_msg_hdr = 0; if (*slots == -ESLOTS_OVERFLOW) { /* overflow - reset */ DBG("reseting due to slots overflow.\n"); /* set the event since message has been read */ ret = -ERANGE; goto end; } end: return (ret); } /* * heci_bh_read_bus_message - bottom half read routine after ISR to * handle the read bus message cmd processing. * * @dev: Device object for our driver * @heci_hdr: header of bus message */ static void heci_bh_read_bus_message(struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr) { struct heci_bus_message *heci_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 *h_stop_req; int i; unsigned char *buffer; /* read the message to our buffer */ buffer = (unsigned char *)dev->rd_msg_buf; ASSERT(heci_hdr->length < sizeof (dev->rd_msg_buf)); heci_read_slots(dev, buffer, heci_hdr->length); heci_msg = (struct heci_bus_message *)buffer; switch (*(uint8_t *)heci_msg) { case HOST_START_RES_CMD: version_res = (struct hbm_host_version_response *)heci_msg; if (version_res->host_version_supported) { dev->version.major_version = HBM_MAJOR_VERSION; dev->version.minor_version = HBM_MINOR_VERSION; } else { dev->version = version_res->me_max_version; } dev->recvd_msg = 1; DBG("host start response message received.\n"); break; case CLIENT_CONNECT_RES_CMD: connect_res = (struct hbm_client_connect_response *)heci_msg; heci_client_connect_response(dev, connect_res); DBG("client connect response message received.\n"); cv_broadcast(&dev->wait_recvd_msg); break; case CLIENT_DISCONNECT_RES_CMD: disconnect_res = (struct hbm_client_connect_response *)heci_msg; heci_client_disconnect_response(dev, disconnect_res); DBG("client disconnect response message received.\n"); cv_broadcast(&dev->wait_recvd_msg); break; case HECI_FLOW_CONTROL_CMD: flow_control = (struct hbm_flow_control *)heci_msg; heci_client_flow_control_response(dev, flow_control); DBG("client flow control response message received.\n"); break; case HOST_CLIENT_PROPERTEIS_RES_CMD: props_res = (struct hbm_props_response *)heci_msg; if (props_res->status != 0) { ASSERT(0); break; } for (i = 0; i < dev->num_heci_me_clients; i++) { if (dev->me_clients[i].client_id == props_res->address) { dev->me_clients[i].props = props_res->client_properties; break; } } dev->recvd_msg = 1; break; case HOST_ENUM_RES_CMD: enum_res = (struct hbm_host_enum_response *)heci_msg; (void) memcpy(dev->heci_me_clients, enum_res->valid_addresses, 32); dev->recvd_msg = 1; break; case HOST_STOP_RES_CMD: dev->heci_state = HECI_DISABLED; DBG("reseting because of FW stop response.\n"); heci_reset(dev, 1); break; case CLIENT_DISCONNECT_REQ_CMD: /* search for client */ disconnect_req = (struct hbm_client_disconnect_request *)heci_msg; heci_client_disconnect_request(dev, disconnect_req); break; case ME_STOP_REQ_CMD: /* prepare stop request */ heci_hdr = (struct heci_msg_hdr *)&dev->ext_msg_buf[0]; heci_hdr->host_addr = 0; heci_hdr->me_addr = 0; heci_hdr->length = sizeof (struct hbm_host_stop_request); heci_hdr->msg_complete = 1; heci_hdr->reserved = 0; h_stop_req = (struct hbm_host_stop_request *)&dev->ext_msg_buf[1]; (void) memset(h_stop_req, 0, sizeof (struct hbm_host_stop_request)); h_stop_req->cmd.cmd = HOST_STOP_REQ_CMD; h_stop_req->reason = DRIVER_STOP_REQUEST; h_stop_req->reserved[0] = 0; h_stop_req->reserved[1] = 0; dev->extra_write_index = 2; break; default: ASSERT(0); break; } } /* * heci_bh_read_pthi_message - bottom half read routine after ISR to * handle the read pthi message data processing. * * @complete_list: An instance of our list structure * @dev: Device object for our driver * @heci_hdr: header of pthi message * * @return 0 on success, <0 on failure. */ static int heci_bh_read_pthi_message(struct io_heci_list *complete_list, struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr) { struct heci_file_private *file_ext; struct heci_cb_private *priv_cb; unsigned char *buffer; ASSERT(heci_hdr->me_addr == dev->iamthif_file_ext.me_client_id); ASSERT(dev->iamthif_state == HECI_IAMTHIF_READING); buffer = (unsigned char *)(dev->iamthif_msg_buf + dev->iamthif_msg_buf_index); ASSERT(sizeof (dev->iamthif_msg_buf) >= (dev->iamthif_msg_buf_index + heci_hdr->length)); heci_read_slots(dev, buffer, heci_hdr->length); dev->iamthif_msg_buf_index += heci_hdr->length; if (!(heci_hdr->msg_complete)) return (0); DBG("pthi_message_buffer_index=%d\n", heci_hdr->length); DBG("completed pthi read.\n "); if (!dev->iamthif_current_cb) return (-ENODEV); priv_cb = dev->iamthif_current_cb; dev->iamthif_current_cb = NULL; file_ext = (struct heci_file_private *)priv_cb->file_private; if (!file_ext) return (-ENODEV); dev->iamthif_stall_timer = 0; priv_cb->information = dev->iamthif_msg_buf_index; priv_cb->read_time = ddi_get_time(); if ((dev->iamthif_ioctl) && (file_ext == &dev->iamthif_file_ext)) { /* found the iamthif cb */ DBG("complete the pthi read cb.\n "); if (&dev->iamthif_file_ext) { DBG("add the pthi read cb to complete.\n "); list_add_tail(&priv_cb->cb_list, &complete_list->heci_cb.cb_list); } } return (0); } /* * _heci_bh_state_ok - check if heci header matches file private data * * @file_ext: private data of the file object * @heci_hdr: header of heci client message * * @return !=0 if matches, 0 if no match. */ static int _heci_bh_state_ok(struct heci_file_private *file_ext, struct heci_msg_hdr *heci_hdr) { return ((file_ext->host_client_id == heci_hdr->host_addr) && (file_ext->me_client_id == heci_hdr->me_addr) && (file_ext->state == HECI_FILE_CONNECTED) && (HECI_READ_COMPLETE != file_ext->reading_state)); } /* * heci_bh_read_client_message - bottom half read routine after ISR to * handle the read heci client message data processing. * * @complete_list: An instance of our list structure * @dev: Device object for our driver * @heci_hdr: header of heci client message * * @return 0 on success, <0 on failure. */ static int heci_bh_read_client_message(struct io_heci_list *complete_list, struct iamt_heci_device *dev, struct heci_msg_hdr *heci_hdr) { struct heci_file_private *file_ext; struct heci_cb_private *priv_cb_pos = NULL, *priv_cb_next = NULL; unsigned char *buffer = NULL; DBG("start client msg\n"); if (!((dev->read_list.status == 0) && !list_empty(&dev->read_list.heci_cb.cb_list))) goto quit; list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &dev->read_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if ((file_ext != NULL) && (_heci_bh_state_ok(file_ext, heci_hdr))) { mutex_enter(&file_ext->read_io_lock); file_ext->reading_state = HECI_READING; buffer = (unsigned char *) (priv_cb_pos->response_buffer.data + priv_cb_pos->information); ASSERT(priv_cb_pos->response_buffer.size >= heci_hdr->length + priv_cb_pos->information); if (priv_cb_pos->response_buffer.size < heci_hdr->length + priv_cb_pos->information) { DBG("message overflow.\n"); list_del(&priv_cb_pos->cb_list); mutex_exit(&file_ext->read_io_lock); return (-ENOMEM); } if (buffer) { heci_read_slots(dev, buffer, heci_hdr->length); } priv_cb_pos->information += heci_hdr->length; if (heci_hdr->msg_complete) { file_ext->status = 0; list_del(&priv_cb_pos->cb_list); mutex_exit(&file_ext->read_io_lock); DBG("completed read host client = %d," "ME client = %d, " "data length = %lu\n", file_ext->host_client_id, file_ext->me_client_id, priv_cb_pos->information); DBG("priv_cb_pos->res_buffer - %s\n", priv_cb_pos->response_buffer.data); list_add_tail(&priv_cb_pos->cb_list, &complete_list->heci_cb.cb_list); } else { mutex_exit(&file_ext->read_io_lock); } break; } } quit: DBG("message read\n"); if (!buffer) { heci_read_slots(dev, (unsigned char *)dev->rd_msg_buf, heci_hdr->length); DBG("discarding message, header=%08x.\n", *(uint32_t *)dev->rd_msg_buf); } return (0); } /* * _heci_bh_iamthif_read: prepare to read iamthif data. * * @dev: Device object for our driver. * @slots: free slots. * * @return 0, OK; otherwise, error. */ static int _heci_bh_iamthif_read(struct iamt_heci_device *dev, int32_t *slots) { if (((*slots) * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_flow_control))) { *slots -= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_flow_control) + 3) / 4; if (!heci_send_flow_control(dev, &dev->iamthif_file_ext)) { DBG("iamthif flow control failed\n"); } else { DBG("iamthif flow control success\n"); dev->iamthif_state = HECI_IAMTHIF_READING; dev->iamthif_flow_control_pending = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_msg_buf_size = 0; dev->iamthif_stall_timer = IAMTHIF_STALL_TIMER; dev->host_buffer_is_empty = host_buffer_is_empty(dev); } return (0); } else { return (-ECOMPLETE_MESSAGE); } } /* * _heci_bh_close: process close related operation. * * @dev: Device object for our driver. * @slots: free slots. * @priv_cb_pos: callback block. * @file_ext: private data of the file object. * @cmpl_list: complete list. * * @return 0, OK; otherwise, error. */ static int _heci_bh_close(struct iamt_heci_device *dev, int32_t *slots, struct heci_cb_private *priv_cb_pos, struct heci_file_private *file_ext, struct io_heci_list *cmpl_list) { if ((*slots * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_client_disconnect_request))) { *slots -= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_client_disconnect_request) + 3) / 4; if (!heci_disconnect(dev, file_ext)) { file_ext->status = 0; priv_cb_pos->information = 0; list_relink_node(&priv_cb_pos->cb_list, &cmpl_list->heci_cb.cb_list); return (-ECOMPLETE_MESSAGE); } else { file_ext->state = HECI_FILE_DISCONNECTING; file_ext->status = 0; priv_cb_pos->information = 0; list_relink_node(&priv_cb_pos->cb_list, &dev->ctrl_rd_list.heci_cb.cb_list); file_ext->timer_count = HECI_CONNECT_TIMEOUT; } } else { /* return the cancel routine */ return (-ECORRUPTED_MESSAGE_HEADER); } return (0); } /* * _heci_hb_close: process read related operation. * * @dev: Device object for our driver. * @slots: free slots. * @priv_cb_pos: callback block. * @file_ext: private data of the file object. * @cmpl_list: complete list. * * @return 0, OK; otherwise, error. */ static int _heci_bh_read(struct iamt_heci_device *dev, int32_t *slots, struct heci_cb_private *priv_cb_pos, struct heci_file_private *file_ext, struct io_heci_list *cmpl_list) { if ((*slots * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_flow_control))) { *slots -= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_flow_control) + 3) / 4; if (!heci_send_flow_control(dev, file_ext)) { file_ext->status = -ENODEV; priv_cb_pos->information = 0; list_relink_node(&priv_cb_pos->cb_list, &cmpl_list->heci_cb.cb_list); return (-ENODEV); } else { list_relink_node(&priv_cb_pos->cb_list, &dev->read_list.heci_cb.cb_list); } } else { /* return the cancel routine */ list_del(&priv_cb_pos->cb_list); return (-ECORRUPTED_MESSAGE_HEADER); } return (0); } /* * _heci_bh_ioctl: process ioctl related operation. * * @dev: Device object for our driver. * @slots: free slots. * @priv_cb_pos: callback block. * @file_ext: private data of the file object. * @cmpl_list: complete list. * * @return 0, OK; otherwise, error. */ static int _heci_bh_ioctl(struct iamt_heci_device *dev, int32_t *slots, struct heci_cb_private *priv_cb_pos, struct heci_file_private *file_ext, struct io_heci_list *cmpl_list) { _NOTE(ARGUNUSED(cmpl_list)); if ((*slots * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_client_connect_request))) { file_ext->state = HECI_FILE_CONNECTING; *slots -= (sizeof (struct heci_msg_hdr) + sizeof (struct hbm_client_connect_request) + 3) / 4; if (!heci_connect(dev, file_ext)) { file_ext->status = -ENODEV; priv_cb_pos->information = 0; list_del(&priv_cb_pos->cb_list); return (-ENODEV); } else { list_relink_node(&priv_cb_pos->cb_list, &dev->ctrl_rd_list.heci_cb.cb_list); file_ext->timer_count = HECI_CONNECT_TIMEOUT; } } else { /* return the cancel routine */ list_del(&priv_cb_pos->cb_list); return (-ECORRUPTED_MESSAGE_HEADER); } return (0); } /* * _heci_bh_cmpl: process completed and no-iamthif operation. * * @dev: Device object for our driver. * @slots: free slots. * @priv_cb_pos: callback block. * @file_ext: private data of the file object. * @cmpl_list: complete list. * * @return 0, OK; otherwise, error. */ static int _heci_bh_cmpl(struct iamt_heci_device *dev, int32_t *slots, struct heci_cb_private *priv_cb_pos, struct heci_file_private *file_ext, struct io_heci_list *cmpl_list) { struct heci_msg_hdr *heci_hdr; if ((*slots * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + (priv_cb_pos->request_buffer.size - priv_cb_pos->information))) { heci_hdr = (struct heci_msg_hdr *)&dev->wr_msg_buf[0]; heci_hdr->host_addr = file_ext->host_client_id; heci_hdr->me_addr = file_ext->me_client_id; heci_hdr->length = ((priv_cb_pos->request_buffer.size) - (priv_cb_pos->information)); heci_hdr->msg_complete = 1; heci_hdr->reserved = 0; DBG("priv_cb_pos->request_buffer.size =%d" "heci_hdr->msg_complete= %d\n", priv_cb_pos->request_buffer.size, heci_hdr->msg_complete); DBG("priv_cb_pos->information =%lu\n", priv_cb_pos->information); DBG("heci_hdr->length =%d\n", heci_hdr->length); *slots -= (sizeof (struct heci_msg_hdr) + heci_hdr->length + 3) / 4; if (!heci_write_message(dev, heci_hdr, (unsigned char *)(priv_cb_pos->request_buffer.data + priv_cb_pos->information), heci_hdr->length)) { file_ext->status = -ENODEV; list_relink_node(&priv_cb_pos->cb_list, &cmpl_list->heci_cb.cb_list); return (-ENODEV); } else { flow_ctrl_reduce(dev, file_ext); file_ext->status = 0; priv_cb_pos->information += heci_hdr->length; list_relink_node(&priv_cb_pos->cb_list, &dev->write_waiting_list.heci_cb.cb_list); } } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { /* buffer is still empty */ heci_hdr = (struct heci_msg_hdr *)&dev->wr_msg_buf[0]; heci_hdr->host_addr = file_ext->host_client_id; heci_hdr->me_addr = file_ext->me_client_id; heci_hdr->length = (*slots * sizeof (uint32_t)) - sizeof (struct heci_msg_hdr); heci_hdr->msg_complete = 0; heci_hdr->reserved = 0; (*slots) -= (sizeof (struct heci_msg_hdr) + heci_hdr->length + 3) / 4; if (!heci_write_message(dev, heci_hdr, (unsigned char *) (priv_cb_pos->request_buffer.data + priv_cb_pos->information), heci_hdr->length)) { file_ext->status = -ENODEV; list_relink_node(&priv_cb_pos->cb_list, &cmpl_list->heci_cb.cb_list); return (-ENODEV); } else { priv_cb_pos->information += heci_hdr->length; DBG("priv_cb_pos->request_buffer.size =%d" " heci_hdr->msg_complete= %d\n", priv_cb_pos->request_buffer.size, heci_hdr->msg_complete); DBG("priv_cb_pos->information =%lu\n", priv_cb_pos->information); DBG("heci_hdr->length =%d\n", heci_hdr->length); } return (-ECOMPLETE_MESSAGE); } else { return (-ECORRUPTED_MESSAGE_HEADER); } return (0); } /* * _heci_bh_cmpl_iamthif: process completed iamthif operation. * * @dev: Device object for our driver. * @slots: free slots. * @priv_cb_pos: callback block. * @file_ext: private data of the file object. * @cmpl_list: complete list. * * @return 0, OK; otherwise, error. */ static int _heci_bh_cmpl_iamthif(struct iamt_heci_device *dev, int32_t *slots, struct heci_cb_private *priv_cb_pos, struct heci_file_private *file_ext, struct io_heci_list *cmpl_list) { struct heci_msg_hdr *heci_hdr; _NOTE(ARGUNUSED(cmpl_list)); if ((*slots * sizeof (uint32_t)) >= (sizeof (struct heci_msg_hdr) + dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index)) { heci_hdr = (struct heci_msg_hdr *)&dev->wr_msg_buf[0]; heci_hdr->host_addr = file_ext->host_client_id; heci_hdr->me_addr = file_ext->me_client_id; heci_hdr->length = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index; heci_hdr->msg_complete = 1; heci_hdr->reserved = 0; *slots -= (sizeof (struct heci_msg_hdr) + heci_hdr->length + 3) / 4; if (!heci_write_message(dev, heci_hdr, (dev->iamthif_msg_buf + dev->iamthif_msg_buf_index), heci_hdr->length)) { dev->iamthif_state = HECI_IAMTHIF_IDLE; file_ext->status = -ENODEV; list_del(&priv_cb_pos->cb_list); return (-ENODEV); } else { flow_ctrl_reduce(dev, file_ext); dev->iamthif_msg_buf_index += heci_hdr->length; priv_cb_pos->information = dev->iamthif_msg_buf_index; file_ext->status = 0; dev->iamthif_state = HECI_IAMTHIF_FLOW_CONTROL; dev->iamthif_flow_control_pending = 1; /* save iamthif cb sent to pthi client */ dev->iamthif_current_cb = priv_cb_pos; list_relink_node(&priv_cb_pos->cb_list, &dev->write_waiting_list.heci_cb.cb_list); } } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { /* buffer is still empty */ heci_hdr = (struct heci_msg_hdr *)&dev->wr_msg_buf[0]; heci_hdr->host_addr = file_ext->host_client_id; heci_hdr->me_addr = file_ext->me_client_id; heci_hdr->length = (*slots * sizeof (uint32_t)) - sizeof (struct heci_msg_hdr); heci_hdr->msg_complete = 0; heci_hdr->reserved = 0; *slots -= (sizeof (struct heci_msg_hdr) + heci_hdr->length + 3) / 4; if (!heci_write_message(dev, heci_hdr, (dev->iamthif_msg_buf + dev->iamthif_msg_buf_index), heci_hdr->length)) { file_ext->status = -ENODEV; list_del(&priv_cb_pos->cb_list); } else { dev->iamthif_msg_buf_index += heci_hdr->length; } return (-ECOMPLETE_MESSAGE); } else { return (-ECORRUPTED_MESSAGE_HEADER); } return (0); } /* * heci_bh_write_handler - bottom half write routine after * ISR to handle the write processing. * * @cmpl_list: An instance of our list structure * @dev: Device object for our driver * @slots: slots to write. * * @return 0 on success, <0 on failure. */ static int heci_bh_write_handler(struct io_heci_list *cmpl_list, struct iamt_heci_device *dev, int32_t *slots) { struct heci_file_private *file_ext; struct heci_cb_private *priv_cb_pos = NULL, *priv_cb_next = NULL; struct io_heci_list *list; int ret; if (!host_buffer_is_empty(dev)) { DBG("host buffer is not empty.\n"); return (0); } dev->write_hang = (uint8_t)-1; *slots = count_empty_write_slots(dev); /* complete all waiting for write CB */ DBG("complete all waiting for write cb.\n"); list = &dev->write_waiting_list; if ((list->status == 0) && !list_empty(&list->heci_cb.cb_list)) { list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &list->heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if (file_ext != NULL) { file_ext->status = 0; list_del(&priv_cb_pos->cb_list); if ((HECI_WRITING == file_ext->writing_state) && (priv_cb_pos->major_file_operations == HECI_WRITING) && (file_ext != &dev->iamthif_file_ext)) { DBG("HECI WRITE COMPLETE\n"); file_ext->writing_state = HECI_WRITE_COMPLETE; list_add_tail(&priv_cb_pos->cb_list, &cmpl_list->heci_cb.cb_list); } if (file_ext == &dev->iamthif_file_ext) { DBG("check iamthif flow control.\n"); if (dev->iamthif_flow_control_pending) { ret = _heci_bh_iamthif_read(dev, slots); if (ret != 0) return (ret); } } } } } if ((dev->stop) && (!dev->wd_pending)) { dev->wd_stoped = 1; cv_broadcast(&dev->wait_stop_wd); return (0); } if (dev->extra_write_index != 0) { DBG("extra_write_index =%d.\n", dev->extra_write_index); if (!heci_write_message(dev, (struct heci_msg_hdr *)&dev->ext_msg_buf[0], (unsigned char *)&dev->ext_msg_buf[1], (dev->extra_write_index - 1) * sizeof (uint32_t))) { DBG("heci_bh_handler: writing msg failed\n"); } *slots -= dev->extra_write_index; dev->extra_write_index = 0; } if (dev->heci_state == HECI_ENABLED) { if (dev->wd_pending && flow_ctrl_creds(dev, &dev->wd_file_ext)) { if (!heci_send_wd(dev)) { DBG("wd send failed.\n"); } else flow_ctrl_reduce(dev, &dev->wd_file_ext); dev->wd_pending = 0; if (dev->wd_timeout != 0) { *slots -= (sizeof (struct heci_msg_hdr) + HECI_START_WD_DATA_SIZE + 3) / 4; dev->wd_due_counter = 2; } else { *slots -= (sizeof (struct heci_msg_hdr) + HECI_WD_PARAMS_SIZE + 3) / 4; dev->wd_due_counter = 0; } } } if (dev->stop) return (~ENODEV); /* complete control write list CB */ if (dev->ctrl_wr_list.status == 0) { /* complete control write list CB */ DBG("complete control write list cb.\n"); list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &dev->ctrl_wr_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if (file_ext == NULL) { list_del(&priv_cb_pos->cb_list); return (-ENODEV); } switch (priv_cb_pos->major_file_operations) { case HECI_CLOSE: /* send disconnect message */ ret = _heci_bh_close(dev, slots, priv_cb_pos, file_ext, cmpl_list); if (ret != 0) return (ret); break; case HECI_READ: /* send flow control message */ ret = _heci_bh_read(dev, slots, priv_cb_pos, file_ext, cmpl_list); if (ret != 0) return (ret); break; case HECI_IOCTL: /* connect message */ if (!other_client_is_connecting(dev, file_ext)) continue; ret = _heci_bh_ioctl(dev, slots, priv_cb_pos, file_ext, cmpl_list); if (ret != 0) return (ret); break; default: ASSERT(0); } } } /* complete write list CB */ if ((dev->write_list.status == 0) && !list_empty(&dev->write_list.heci_cb.cb_list)) { DBG("complete write list cb.\n"); list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &dev->write_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if (file_ext != NULL) { if (file_ext != &dev->iamthif_file_ext) { if (!flow_ctrl_creds(dev, file_ext)) { DBG("No flow control" " credentials for client" " %d, not sending.\n", file_ext->host_client_id); continue; } ret = _heci_bh_cmpl(dev, slots, priv_cb_pos, file_ext, cmpl_list); if (ret != 0) return (ret); } else if (file_ext == &dev->iamthif_file_ext) { /* IAMTHIF IOCTL */ DBG("complete pthi write cb.\n"); if (!flow_ctrl_creds(dev, file_ext)) { DBG("No flow control" " credentials for pthi" " client %d.\n", file_ext->host_client_id); continue; } ret = _heci_bh_cmpl_iamthif(dev, slots, priv_cb_pos, file_ext, cmpl_list); if (ret != 0) return (ret); } } } } return (0); } /* * is_treat_specially_client - check if the message belong * to the file private data. * * @file_ext: private data of the file object * @rs: connect response bus message * @dev: Device object for our driver * * @return 0 on success, <0 on failure. */ static int is_treat_specially_client(struct heci_file_private *file_ext, struct hbm_client_connect_response *rs) { int ret = 0; if ((file_ext->host_client_id == rs->host_addr) && (file_ext->me_client_id == rs->me_addr)) { if (rs->status == 0) { DBG("client connect status = 0x%08x.\n", rs->status); file_ext->state = HECI_FILE_CONNECTED; file_ext->status = 0; } else { DBG("client connect status = 0x%08x.\n", rs->status); file_ext->state = HECI_FILE_DISCONNECTED; file_ext->status = -ENODEV; } ret = 1; } DBG("client state = %d.\n", file_ext->state); return (ret); } /* * heci_client_connect_response - connect response bh routine * * @dev: Device object for our driver * @rs: connect response bus message */ static void heci_client_connect_response(struct iamt_heci_device *dev, struct hbm_client_connect_response *rs) { struct heci_file_private *file_ext; struct heci_cb_private *priv_cb_pos = NULL, *priv_cb_next = NULL; /* if WD or iamthif client treat specially */ if ((is_treat_specially_client(&(dev->wd_file_ext), rs)) || (is_treat_specially_client(&(dev->iamthif_file_ext), rs))) return; if (dev->ctrl_rd_list.status == 0 && !list_empty(&dev->ctrl_rd_list.heci_cb.cb_list)) { list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &dev->ctrl_rd_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if (file_ext == NULL) { list_del(&priv_cb_pos->cb_list); return; } if (HECI_IOCTL == priv_cb_pos->major_file_operations) { if (is_treat_specially_client(file_ext, rs)) { list_del(&priv_cb_pos->cb_list); file_ext->status = 0; file_ext->timer_count = 0; break; } } } } } /* * heci_client_disconnect_response - disconnect response bh routine * * @dev: Device object for our driver * @rs: disconnect response bus message */ static void heci_client_disconnect_response(struct iamt_heci_device *dev, struct hbm_client_connect_response *rs) { struct heci_file_private *file_ext; struct heci_cb_private *priv_cb_pos = NULL, *priv_cb_next = NULL; if (dev->ctrl_rd_list.status == 0 && !list_empty(&dev->ctrl_rd_list.heci_cb.cb_list)) { list_for_each_entry_safe(priv_cb_pos, priv_cb_next, &dev->ctrl_rd_list.heci_cb.cb_list, cb_list, struct heci_cb_private) { file_ext = (struct heci_file_private *) priv_cb_pos->file_private; if (file_ext == NULL) { list_del(&priv_cb_pos->cb_list); return; } DBG("list_for_each_entry_safe in ctrl_rd_list.\n"); if ((file_ext->host_client_id == rs->host_addr) && (file_ext->me_client_id == rs->me_addr)) { list_del(&priv_cb_pos->cb_list); if (rs->status == 0) { file_ext->state = HECI_FILE_DISCONNECTED; } file_ext->status = 0; file_ext->timer_count = 0; break; } } } } /* * same_flow_addr - tell they have same address. * * @file: private data of the file object. * @flow: flow control. * * @return !=0, same; 0,not. */ static int same_flow_addr(struct heci_file_private *file, struct hbm_flow_control *flow) { return ((file->host_client_id == flow->host_addr) && (file->me_client_id == flow->me_addr)); } /* * add_single_flow_creds - add single buffer credentials. * * @file: private data ot the file object. * @flow: flow control. */ static void add_single_flow_creds(struct iamt_heci_device *dev, struct hbm_flow_control *flow) { struct heci_me_client *client; int i; for (i = 0; i < dev->num_heci_me_clients; i++) { client = &dev->me_clients[i]; if ((client != NULL) && (flow->me_addr == client->client_id)) { if (client->props.single_recv_buf != 0) { client->flow_ctrl_creds++; DBG("recv flow ctrl msg ME %d (single).\n", flow->me_addr); DBG("flow control credentials=%d.\n", client->flow_ctrl_creds); } else { ASSERT(0); /* error in flow control */ } } } } /* * heci_client_flow_control_response - flow control response bh routine * * @dev: Device object for our driver * @flow_control: flow control response bus message */ static void heci_client_flow_control_response(struct iamt_heci_device *dev, struct hbm_flow_control *flow_control) { struct heci_file_private *file_pos = NULL; struct heci_file_private *file_next = NULL; if (flow_control->host_addr == 0) { /* single receive buffer */ add_single_flow_creds(dev, flow_control); } else { /* normal connection */ list_for_each_entry_safe(file_pos, file_next, &dev->file_list, link, struct heci_file_private) { DBG("list_for_each_entry_safe in file_list\n"); DBG("file_ext of host client %d ME client %d.\n", file_pos->host_client_id, file_pos->me_client_id); DBG("flow ctrl msg for host %d ME %d.\n", flow_control->host_addr, flow_control->me_addr); if (same_flow_addr(file_pos, flow_control)) { DBG("recv ctrl msg for host %d ME %d.\n", flow_control->host_addr, flow_control->me_addr); file_pos->flow_ctrl_creds++; DBG("flow control credentials=%d.\n", file_pos->flow_ctrl_creds); break; } } } } /* * same_disconn_addr - tell they have same address * * @file: private data of the file object. * @disconn: disconnection request. * * @return !=0, same; 0,not. */ static int same_disconn_addr(struct heci_file_private *file, struct hbm_client_disconnect_request *disconn) { return ((file->host_client_id == disconn->host_addr) && (file->me_client_id == disconn->me_addr)); } /* * heci_client_disconnect_request - disconnect request bh routine * * @dev: Device object for our driver. * @disconnect_req: disconnect request bus message. */ static void heci_client_disconnect_request(struct iamt_heci_device *dev, struct hbm_client_disconnect_request *disconnect_req) { struct heci_msg_hdr *heci_hdr; struct hbm_client_connect_response *disconnect_res; struct heci_file_private *file_pos = NULL; struct heci_file_private *file_next = NULL; list_for_each_entry_safe(file_pos, file_next, &dev->file_list, link, struct heci_file_private) { if (same_disconn_addr(file_pos, disconnect_req)) { DBG("disconnect request host client %d ME client %d.\n", disconnect_req->host_addr, disconnect_req->me_addr); file_pos->state = HECI_FILE_DISCONNECTED; file_pos->timer_count = 0; if (file_pos == &dev->wd_file_ext) { dev->wd_due_counter = 0; dev->wd_pending = 0; } else if (file_pos == &dev->iamthif_file_ext) dev->iamthif_timer = 0; /* prepare disconnect response */ heci_hdr = (struct heci_msg_hdr *)&dev->ext_msg_buf[0]; heci_hdr->host_addr = 0; heci_hdr->me_addr = 0; heci_hdr->length = sizeof (struct hbm_client_connect_response); heci_hdr->msg_complete = 1; heci_hdr->reserved = 0; disconnect_res = (struct hbm_client_connect_response *) &dev->ext_msg_buf[1]; disconnect_res->host_addr = file_pos->host_client_id; disconnect_res->me_addr = file_pos->me_client_id; *(uint8_t *)(&disconnect_res->cmd) = CLIENT_DISCONNECT_RES_CMD; disconnect_res->status = 0; dev->extra_write_index = 2; break; } } } /* * heci_timer - timer function. * * @data: pointer to the device structure * * NOTE: This function is called by timer interrupt work */ void heci_wd_timer(void *data) { struct iamt_heci_device *dev = (struct iamt_heci_device *)data; DBG("send watchdog.\n"); mutex_enter(&dev->device_lock); if (dev->heci_state != HECI_ENABLED) { dev->wd_timer = timeout(heci_wd_timer, data, 2 * HZ); mutex_exit(&dev->device_lock); return; } if (dev->wd_file_ext.state != HECI_FILE_CONNECTED) { dev->wd_timer = timeout(heci_wd_timer, data, 2 * HZ); mutex_exit(&dev->device_lock); return; } /* Watchdog */ if ((dev->wd_due_counter != 0) && (dev->wd_bypass == 0)) { if (--dev->wd_due_counter == 0) { if (dev->host_buffer_is_empty && flow_ctrl_creds(dev, &dev->wd_file_ext)) { dev->host_buffer_is_empty = 0; if (!heci_send_wd(dev)) { DBG("wd send failed.\n"); } else { flow_ctrl_reduce(dev, &dev->wd_file_ext); } if (dev->wd_timeout != 0) dev->wd_due_counter = 2; else dev->wd_due_counter = 0; } else dev->wd_pending = 1; } } if (dev->iamthif_stall_timer != 0) { if (--dev->iamthif_stall_timer == 0) { DBG("reseting because of hang to PTHI.\n"); heci_reset(dev, 1); dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = 0; dev->iamthif_ioctl = 1; dev->iamthif_state = HECI_IAMTHIF_IDLE; dev->iamthif_timer = 0; mutex_exit(&dev->device_lock); if (dev->iamthif_current_cb) heci_free_cb_private(dev->iamthif_current_cb); mutex_enter(&dev->device_lock); dev->iamthif_file_object = NULL; dev->iamthif_current_cb = NULL; run_next_iamthif_cmd(dev); } } dev->wd_timer = timeout(heci_wd_timer, data, 2 * HZ); mutex_exit(&dev->device_lock); }