162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2003-2018, Intel Corporation. All rights reserved. 462306a36Sopenharmony_ci * Intel Management Engine Interface (Intel MEI) Linux driver 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/export.h> 862306a36Sopenharmony_ci#include <linux/kthread.h> 962306a36Sopenharmony_ci#include <linux/interrupt.h> 1062306a36Sopenharmony_ci#include <linux/fs.h> 1162306a36Sopenharmony_ci#include <linux/jiffies.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/mei.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "mei_dev.h" 1862306a36Sopenharmony_ci#include "hbm.h" 1962306a36Sopenharmony_ci#include "client.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/** 2362306a36Sopenharmony_ci * mei_irq_compl_handler - dispatch complete handlers 2462306a36Sopenharmony_ci * for the completed callbacks 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * @dev: mei device 2762306a36Sopenharmony_ci * @cmpl_list: list of completed cbs 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_civoid mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct mei_cl_cb *cb, *next; 3262306a36Sopenharmony_ci struct mei_cl *cl; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci list_for_each_entry_safe(cb, next, cmpl_list, list) { 3562306a36Sopenharmony_ci cl = cb->cl; 3662306a36Sopenharmony_ci list_del_init(&cb->list); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci dev_dbg(dev->dev, "completing call back.\n"); 3962306a36Sopenharmony_ci mei_cl_complete(cl, cb); 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mei_irq_compl_handler); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci/** 4562306a36Sopenharmony_ci * mei_cl_hbm_equal - check if hbm is addressed to the client 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * @cl: host client 4862306a36Sopenharmony_ci * @mei_hdr: header of mei client message 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * Return: true if matches, false otherwise 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_cistatic inline int mei_cl_hbm_equal(struct mei_cl *cl, 5362306a36Sopenharmony_ci struct mei_msg_hdr *mei_hdr) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci return mei_cl_host_addr(cl) == mei_hdr->host_addr && 5662306a36Sopenharmony_ci mei_cl_me_id(cl) == mei_hdr->me_addr; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/** 6062306a36Sopenharmony_ci * mei_irq_discard_msg - discard received message 6162306a36Sopenharmony_ci * 6262306a36Sopenharmony_ci * @dev: mei device 6362306a36Sopenharmony_ci * @hdr: message header 6462306a36Sopenharmony_ci * @discard_len: the length of the message to discard (excluding header) 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_cistatic void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr, 6762306a36Sopenharmony_ci size_t discard_len) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci if (hdr->dma_ring) { 7062306a36Sopenharmony_ci mei_dma_ring_read(dev, NULL, 7162306a36Sopenharmony_ci hdr->extension[dev->rd_msg_hdr_count - 2]); 7262306a36Sopenharmony_ci discard_len = 0; 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci /* 7562306a36Sopenharmony_ci * no need to check for size as it is guarantied 7662306a36Sopenharmony_ci * that length fits into rd_msg_buf 7762306a36Sopenharmony_ci */ 7862306a36Sopenharmony_ci mei_read_slots(dev, dev->rd_msg_buf, discard_len); 7962306a36Sopenharmony_ci dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", 8062306a36Sopenharmony_ci MEI_HDR_PRM(hdr)); 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci/** 8462306a36Sopenharmony_ci * mei_cl_irq_read_msg - process client message 8562306a36Sopenharmony_ci * 8662306a36Sopenharmony_ci * @cl: reading client 8762306a36Sopenharmony_ci * @mei_hdr: header of mei client message 8862306a36Sopenharmony_ci * @meta: extend meta header 8962306a36Sopenharmony_ci * @cmpl_list: completion list 9062306a36Sopenharmony_ci * 9162306a36Sopenharmony_ci * Return: always 0 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_cistatic int mei_cl_irq_read_msg(struct mei_cl *cl, 9462306a36Sopenharmony_ci struct mei_msg_hdr *mei_hdr, 9562306a36Sopenharmony_ci struct mei_ext_meta_hdr *meta, 9662306a36Sopenharmony_ci struct list_head *cmpl_list) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct mei_device *dev = cl->dev; 9962306a36Sopenharmony_ci struct mei_cl_cb *cb; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci struct mei_ext_hdr_vtag *vtag_hdr = NULL; 10262306a36Sopenharmony_ci struct mei_ext_hdr_gsc_f2h *gsc_f2h = NULL; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci size_t buf_sz; 10562306a36Sopenharmony_ci u32 length; 10662306a36Sopenharmony_ci u32 ext_len; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci length = mei_hdr->length; 10962306a36Sopenharmony_ci ext_len = 0; 11062306a36Sopenharmony_ci if (mei_hdr->extended) { 11162306a36Sopenharmony_ci ext_len = sizeof(*meta) + mei_slots2data(meta->size); 11262306a36Sopenharmony_ci length -= ext_len; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list); 11662306a36Sopenharmony_ci if (!cb) { 11762306a36Sopenharmony_ci if (!mei_cl_is_fixed_address(cl)) { 11862306a36Sopenharmony_ci cl_err(dev, cl, "pending read cb not found\n"); 11962306a36Sopenharmony_ci goto discard; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci cb = mei_cl_alloc_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, cl->fp); 12262306a36Sopenharmony_ci if (!cb) 12362306a36Sopenharmony_ci goto discard; 12462306a36Sopenharmony_ci list_add_tail(&cb->list, &cl->rd_pending); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (mei_hdr->extended) { 12862306a36Sopenharmony_ci struct mei_ext_hdr *ext = mei_ext_begin(meta); 12962306a36Sopenharmony_ci do { 13062306a36Sopenharmony_ci switch (ext->type) { 13162306a36Sopenharmony_ci case MEI_EXT_HDR_VTAG: 13262306a36Sopenharmony_ci vtag_hdr = (struct mei_ext_hdr_vtag *)ext; 13362306a36Sopenharmony_ci break; 13462306a36Sopenharmony_ci case MEI_EXT_HDR_GSC: 13562306a36Sopenharmony_ci gsc_f2h = (struct mei_ext_hdr_gsc_f2h *)ext; 13662306a36Sopenharmony_ci cb->ext_hdr = kzalloc(sizeof(*gsc_f2h), GFP_KERNEL); 13762306a36Sopenharmony_ci if (!cb->ext_hdr) { 13862306a36Sopenharmony_ci cb->status = -ENOMEM; 13962306a36Sopenharmony_ci goto discard; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci break; 14262306a36Sopenharmony_ci case MEI_EXT_HDR_NONE: 14362306a36Sopenharmony_ci fallthrough; 14462306a36Sopenharmony_ci default: 14562306a36Sopenharmony_ci cl_err(dev, cl, "unknown extended header\n"); 14662306a36Sopenharmony_ci cb->status = -EPROTO; 14762306a36Sopenharmony_ci break; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci ext = mei_ext_next(ext); 15162306a36Sopenharmony_ci } while (!mei_ext_last(meta, ext)); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (!vtag_hdr && !gsc_f2h) { 15462306a36Sopenharmony_ci cl_dbg(dev, cl, "no vtag or gsc found in extended header.\n"); 15562306a36Sopenharmony_ci cb->status = -EPROTO; 15662306a36Sopenharmony_ci goto discard; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (vtag_hdr) { 16162306a36Sopenharmony_ci cl_dbg(dev, cl, "vtag: %d\n", vtag_hdr->vtag); 16262306a36Sopenharmony_ci if (cb->vtag && cb->vtag != vtag_hdr->vtag) { 16362306a36Sopenharmony_ci cl_err(dev, cl, "mismatched tag: %d != %d\n", 16462306a36Sopenharmony_ci cb->vtag, vtag_hdr->vtag); 16562306a36Sopenharmony_ci cb->status = -EPROTO; 16662306a36Sopenharmony_ci goto discard; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci cb->vtag = vtag_hdr->vtag; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (gsc_f2h) { 17262306a36Sopenharmony_ci u32 ext_hdr_len = mei_ext_hdr_len(&gsc_f2h->hdr); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (!dev->hbm_f_gsc_supported) { 17562306a36Sopenharmony_ci cl_err(dev, cl, "gsc extended header is not supported\n"); 17662306a36Sopenharmony_ci cb->status = -EPROTO; 17762306a36Sopenharmony_ci goto discard; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (length) { 18162306a36Sopenharmony_ci cl_err(dev, cl, "no data allowed in cb with gsc\n"); 18262306a36Sopenharmony_ci cb->status = -EPROTO; 18362306a36Sopenharmony_ci goto discard; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci if (ext_hdr_len > sizeof(*gsc_f2h)) { 18662306a36Sopenharmony_ci cl_err(dev, cl, "gsc extended header is too big %u\n", ext_hdr_len); 18762306a36Sopenharmony_ci cb->status = -EPROTO; 18862306a36Sopenharmony_ci goto discard; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci memcpy(cb->ext_hdr, gsc_f2h, ext_hdr_len); 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!mei_cl_is_connected(cl)) { 19462306a36Sopenharmony_ci cl_dbg(dev, cl, "not connected\n"); 19562306a36Sopenharmony_ci cb->status = -ENODEV; 19662306a36Sopenharmony_ci goto discard; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (mei_hdr->dma_ring) 20062306a36Sopenharmony_ci length = mei_hdr->extension[mei_data2slots(ext_len)]; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci buf_sz = length + cb->buf_idx; 20362306a36Sopenharmony_ci /* catch for integer overflow */ 20462306a36Sopenharmony_ci if (buf_sz < cb->buf_idx) { 20562306a36Sopenharmony_ci cl_err(dev, cl, "message is too big len %d idx %zu\n", 20662306a36Sopenharmony_ci length, cb->buf_idx); 20762306a36Sopenharmony_ci cb->status = -EMSGSIZE; 20862306a36Sopenharmony_ci goto discard; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (cb->buf.size < buf_sz) { 21262306a36Sopenharmony_ci cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n", 21362306a36Sopenharmony_ci cb->buf.size, length, cb->buf_idx); 21462306a36Sopenharmony_ci cb->status = -EMSGSIZE; 21562306a36Sopenharmony_ci goto discard; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (mei_hdr->dma_ring) { 21962306a36Sopenharmony_ci mei_dma_ring_read(dev, cb->buf.data + cb->buf_idx, length); 22062306a36Sopenharmony_ci /* for DMA read 0 length to generate interrupt to the device */ 22162306a36Sopenharmony_ci mei_read_slots(dev, cb->buf.data + cb->buf_idx, 0); 22262306a36Sopenharmony_ci } else { 22362306a36Sopenharmony_ci mei_read_slots(dev, cb->buf.data + cb->buf_idx, length); 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci cb->buf_idx += length; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (mei_hdr->msg_complete) { 22962306a36Sopenharmony_ci cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx); 23062306a36Sopenharmony_ci list_move_tail(&cb->list, cmpl_list); 23162306a36Sopenharmony_ci } else { 23262306a36Sopenharmony_ci pm_runtime_mark_last_busy(dev->dev); 23362306a36Sopenharmony_ci pm_request_autosuspend(dev->dev); 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci return 0; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cidiscard: 23962306a36Sopenharmony_ci if (cb) 24062306a36Sopenharmony_ci list_move_tail(&cb->list, cmpl_list); 24162306a36Sopenharmony_ci mei_irq_discard_msg(dev, mei_hdr, length); 24262306a36Sopenharmony_ci return 0; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci/** 24662306a36Sopenharmony_ci * mei_cl_irq_disconnect_rsp - send disconnection response message 24762306a36Sopenharmony_ci * 24862306a36Sopenharmony_ci * @cl: client 24962306a36Sopenharmony_ci * @cb: callback block. 25062306a36Sopenharmony_ci * @cmpl_list: complete list. 25162306a36Sopenharmony_ci * 25262306a36Sopenharmony_ci * Return: 0, OK; otherwise, error. 25362306a36Sopenharmony_ci */ 25462306a36Sopenharmony_cistatic int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb, 25562306a36Sopenharmony_ci struct list_head *cmpl_list) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci struct mei_device *dev = cl->dev; 25862306a36Sopenharmony_ci u32 msg_slots; 25962306a36Sopenharmony_ci int slots; 26062306a36Sopenharmony_ci int ret; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_response)); 26362306a36Sopenharmony_ci slots = mei_hbuf_empty_slots(dev); 26462306a36Sopenharmony_ci if (slots < 0) 26562306a36Sopenharmony_ci return -EOVERFLOW; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if ((u32)slots < msg_slots) 26862306a36Sopenharmony_ci return -EMSGSIZE; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ret = mei_hbm_cl_disconnect_rsp(dev, cl); 27162306a36Sopenharmony_ci list_move_tail(&cb->list, cmpl_list); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci return ret; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci/** 27762306a36Sopenharmony_ci * mei_cl_irq_read - processes client read related operation from the 27862306a36Sopenharmony_ci * interrupt thread context - request for flow control credits 27962306a36Sopenharmony_ci * 28062306a36Sopenharmony_ci * @cl: client 28162306a36Sopenharmony_ci * @cb: callback block. 28262306a36Sopenharmony_ci * @cmpl_list: complete list. 28362306a36Sopenharmony_ci * 28462306a36Sopenharmony_ci * Return: 0, OK; otherwise, error. 28562306a36Sopenharmony_ci */ 28662306a36Sopenharmony_cistatic int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, 28762306a36Sopenharmony_ci struct list_head *cmpl_list) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct mei_device *dev = cl->dev; 29062306a36Sopenharmony_ci u32 msg_slots; 29162306a36Sopenharmony_ci int slots; 29262306a36Sopenharmony_ci int ret; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (!list_empty(&cl->rd_pending)) 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci msg_slots = mei_hbm2slots(sizeof(struct hbm_flow_control)); 29862306a36Sopenharmony_ci slots = mei_hbuf_empty_slots(dev); 29962306a36Sopenharmony_ci if (slots < 0) 30062306a36Sopenharmony_ci return -EOVERFLOW; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if ((u32)slots < msg_slots) 30362306a36Sopenharmony_ci return -EMSGSIZE; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci ret = mei_hbm_cl_flow_control_req(dev, cl); 30662306a36Sopenharmony_ci if (ret) { 30762306a36Sopenharmony_ci cl->status = ret; 30862306a36Sopenharmony_ci cb->buf_idx = 0; 30962306a36Sopenharmony_ci list_move_tail(&cb->list, cmpl_list); 31062306a36Sopenharmony_ci return ret; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci pm_runtime_mark_last_busy(dev->dev); 31462306a36Sopenharmony_ci pm_request_autosuspend(dev->dev); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci list_move_tail(&cb->list, &cl->rd_pending); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return 0; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic inline bool hdr_is_hbm(struct mei_msg_hdr *mei_hdr) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci return mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0; 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0; 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic inline int hdr_is_valid(u32 msg_hdr) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci struct mei_msg_hdr *mei_hdr; 33462306a36Sopenharmony_ci u32 expected_len = 0; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci mei_hdr = (struct mei_msg_hdr *)&msg_hdr; 33762306a36Sopenharmony_ci if (!msg_hdr || mei_hdr->reserved) 33862306a36Sopenharmony_ci return -EBADMSG; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (mei_hdr->dma_ring) 34162306a36Sopenharmony_ci expected_len += MEI_SLOT_SIZE; 34262306a36Sopenharmony_ci if (mei_hdr->extended) 34362306a36Sopenharmony_ci expected_len += MEI_SLOT_SIZE; 34462306a36Sopenharmony_ci if (mei_hdr->length < expected_len) 34562306a36Sopenharmony_ci return -EBADMSG; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci return 0; 34862306a36Sopenharmony_ci} 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci/** 35162306a36Sopenharmony_ci * mei_irq_read_handler - bottom half read routine after ISR to 35262306a36Sopenharmony_ci * handle the read processing. 35362306a36Sopenharmony_ci * 35462306a36Sopenharmony_ci * @dev: the device structure 35562306a36Sopenharmony_ci * @cmpl_list: An instance of our list structure 35662306a36Sopenharmony_ci * @slots: slots to read. 35762306a36Sopenharmony_ci * 35862306a36Sopenharmony_ci * Return: 0 on success, <0 on failure. 35962306a36Sopenharmony_ci */ 36062306a36Sopenharmony_ciint mei_irq_read_handler(struct mei_device *dev, 36162306a36Sopenharmony_ci struct list_head *cmpl_list, s32 *slots) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci struct mei_msg_hdr *mei_hdr; 36462306a36Sopenharmony_ci struct mei_ext_meta_hdr *meta_hdr = NULL; 36562306a36Sopenharmony_ci struct mei_cl *cl; 36662306a36Sopenharmony_ci int ret; 36762306a36Sopenharmony_ci u32 hdr_size_left; 36862306a36Sopenharmony_ci u32 hdr_size_ext; 36962306a36Sopenharmony_ci int i; 37062306a36Sopenharmony_ci int ext_hdr_end; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (!dev->rd_msg_hdr[0]) { 37362306a36Sopenharmony_ci dev->rd_msg_hdr[0] = mei_read_hdr(dev); 37462306a36Sopenharmony_ci dev->rd_msg_hdr_count = 1; 37562306a36Sopenharmony_ci (*slots)--; 37662306a36Sopenharmony_ci dev_dbg(dev->dev, "slots =%08x.\n", *slots); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci ret = hdr_is_valid(dev->rd_msg_hdr[0]); 37962306a36Sopenharmony_ci if (ret) { 38062306a36Sopenharmony_ci dev_err(dev->dev, "corrupted message header 0x%08X\n", 38162306a36Sopenharmony_ci dev->rd_msg_hdr[0]); 38262306a36Sopenharmony_ci goto end; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr; 38762306a36Sopenharmony_ci dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (mei_slots2data(*slots) < mei_hdr->length) { 39062306a36Sopenharmony_ci dev_err(dev->dev, "less data available than length=%08x.\n", 39162306a36Sopenharmony_ci *slots); 39262306a36Sopenharmony_ci /* we can't read the message */ 39362306a36Sopenharmony_ci ret = -ENODATA; 39462306a36Sopenharmony_ci goto end; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci ext_hdr_end = 1; 39862306a36Sopenharmony_ci hdr_size_left = mei_hdr->length; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci if (mei_hdr->extended) { 40162306a36Sopenharmony_ci if (!dev->rd_msg_hdr[1]) { 40262306a36Sopenharmony_ci dev->rd_msg_hdr[1] = mei_read_hdr(dev); 40362306a36Sopenharmony_ci dev->rd_msg_hdr_count++; 40462306a36Sopenharmony_ci (*slots)--; 40562306a36Sopenharmony_ci dev_dbg(dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]); 40862306a36Sopenharmony_ci if (check_add_overflow((u32)sizeof(*meta_hdr), 40962306a36Sopenharmony_ci mei_slots2data(meta_hdr->size), 41062306a36Sopenharmony_ci &hdr_size_ext)) { 41162306a36Sopenharmony_ci dev_err(dev->dev, "extended message size too big %d\n", 41262306a36Sopenharmony_ci meta_hdr->size); 41362306a36Sopenharmony_ci return -EBADMSG; 41462306a36Sopenharmony_ci } 41562306a36Sopenharmony_ci if (hdr_size_left < hdr_size_ext) { 41662306a36Sopenharmony_ci dev_err(dev->dev, "corrupted message header len %d\n", 41762306a36Sopenharmony_ci mei_hdr->length); 41862306a36Sopenharmony_ci return -EBADMSG; 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci hdr_size_left -= hdr_size_ext; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci ext_hdr_end = meta_hdr->size + 2; 42362306a36Sopenharmony_ci for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) { 42462306a36Sopenharmony_ci dev->rd_msg_hdr[i] = mei_read_hdr(dev); 42562306a36Sopenharmony_ci dev_dbg(dev->dev, "extended header %d is %08x\n", i, 42662306a36Sopenharmony_ci dev->rd_msg_hdr[i]); 42762306a36Sopenharmony_ci dev->rd_msg_hdr_count++; 42862306a36Sopenharmony_ci (*slots)--; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci if (mei_hdr->dma_ring) { 43362306a36Sopenharmony_ci if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) { 43462306a36Sopenharmony_ci dev_err(dev->dev, "corrupted message header len %d\n", 43562306a36Sopenharmony_ci mei_hdr->length); 43662306a36Sopenharmony_ci return -EBADMSG; 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci dev->rd_msg_hdr[ext_hdr_end] = mei_read_hdr(dev); 44062306a36Sopenharmony_ci dev->rd_msg_hdr_count++; 44162306a36Sopenharmony_ci (*slots)--; 44262306a36Sopenharmony_ci mei_hdr->length -= sizeof(dev->rd_msg_hdr[ext_hdr_end]); 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci /* HBM message */ 44662306a36Sopenharmony_ci if (hdr_is_hbm(mei_hdr)) { 44762306a36Sopenharmony_ci ret = mei_hbm_dispatch(dev, mei_hdr); 44862306a36Sopenharmony_ci if (ret) { 44962306a36Sopenharmony_ci dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n", 45062306a36Sopenharmony_ci ret); 45162306a36Sopenharmony_ci goto end; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci goto reset_slots; 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci /* find recipient cl */ 45762306a36Sopenharmony_ci list_for_each_entry(cl, &dev->file_list, link) { 45862306a36Sopenharmony_ci if (mei_cl_hbm_equal(cl, mei_hdr)) { 45962306a36Sopenharmony_ci cl_dbg(dev, cl, "got a message\n"); 46062306a36Sopenharmony_ci ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list); 46162306a36Sopenharmony_ci goto reset_slots; 46262306a36Sopenharmony_ci } 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci /* if no recipient cl was found we assume corrupted header */ 46662306a36Sopenharmony_ci /* A message for not connected fixed address clients 46762306a36Sopenharmony_ci * should be silently discarded 46862306a36Sopenharmony_ci * On power down client may be force cleaned, 46962306a36Sopenharmony_ci * silently discard such messages 47062306a36Sopenharmony_ci */ 47162306a36Sopenharmony_ci if (hdr_is_fixed(mei_hdr) || 47262306a36Sopenharmony_ci dev->dev_state == MEI_DEV_POWER_DOWN) { 47362306a36Sopenharmony_ci mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length); 47462306a36Sopenharmony_ci ret = 0; 47562306a36Sopenharmony_ci goto reset_slots; 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci dev_err(dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); 47862306a36Sopenharmony_ci ret = -EBADMSG; 47962306a36Sopenharmony_ci goto end; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cireset_slots: 48262306a36Sopenharmony_ci /* reset the number of slots and header */ 48362306a36Sopenharmony_ci memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr)); 48462306a36Sopenharmony_ci dev->rd_msg_hdr_count = 0; 48562306a36Sopenharmony_ci *slots = mei_count_full_read_slots(dev); 48662306a36Sopenharmony_ci if (*slots == -EOVERFLOW) { 48762306a36Sopenharmony_ci /* overflow - reset */ 48862306a36Sopenharmony_ci dev_err(dev->dev, "resetting due to slots overflow.\n"); 48962306a36Sopenharmony_ci /* set the event since message has been read */ 49062306a36Sopenharmony_ci ret = -ERANGE; 49162306a36Sopenharmony_ci goto end; 49262306a36Sopenharmony_ci } 49362306a36Sopenharmony_ciend: 49462306a36Sopenharmony_ci return ret; 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mei_irq_read_handler); 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci/** 50062306a36Sopenharmony_ci * mei_irq_write_handler - dispatch write requests 50162306a36Sopenharmony_ci * after irq received 50262306a36Sopenharmony_ci * 50362306a36Sopenharmony_ci * @dev: the device structure 50462306a36Sopenharmony_ci * @cmpl_list: An instance of our list structure 50562306a36Sopenharmony_ci * 50662306a36Sopenharmony_ci * Return: 0 on success, <0 on failure. 50762306a36Sopenharmony_ci */ 50862306a36Sopenharmony_ciint mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci struct mei_cl *cl; 51262306a36Sopenharmony_ci struct mei_cl_cb *cb, *next; 51362306a36Sopenharmony_ci s32 slots; 51462306a36Sopenharmony_ci int ret; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci if (!mei_hbuf_acquire(dev)) 51862306a36Sopenharmony_ci return 0; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci slots = mei_hbuf_empty_slots(dev); 52162306a36Sopenharmony_ci if (slots < 0) 52262306a36Sopenharmony_ci return -EOVERFLOW; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci if (slots == 0) 52562306a36Sopenharmony_ci return -EMSGSIZE; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci /* complete all waiting for write CB */ 52862306a36Sopenharmony_ci dev_dbg(dev->dev, "complete all waiting for write cb.\n"); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) { 53162306a36Sopenharmony_ci cl = cb->cl; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci cl->status = 0; 53462306a36Sopenharmony_ci cl_dbg(dev, cl, "MEI WRITE COMPLETE\n"); 53562306a36Sopenharmony_ci cl->writing_state = MEI_WRITE_COMPLETE; 53662306a36Sopenharmony_ci list_move_tail(&cb->list, cmpl_list); 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci /* complete control write list CB */ 54062306a36Sopenharmony_ci dev_dbg(dev->dev, "complete control write list cb.\n"); 54162306a36Sopenharmony_ci list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) { 54262306a36Sopenharmony_ci cl = cb->cl; 54362306a36Sopenharmony_ci switch (cb->fop_type) { 54462306a36Sopenharmony_ci case MEI_FOP_DISCONNECT: 54562306a36Sopenharmony_ci /* send disconnect message */ 54662306a36Sopenharmony_ci ret = mei_cl_irq_disconnect(cl, cb, cmpl_list); 54762306a36Sopenharmony_ci if (ret) 54862306a36Sopenharmony_ci return ret; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci break; 55162306a36Sopenharmony_ci case MEI_FOP_READ: 55262306a36Sopenharmony_ci /* send flow control message */ 55362306a36Sopenharmony_ci ret = mei_cl_irq_read(cl, cb, cmpl_list); 55462306a36Sopenharmony_ci if (ret) 55562306a36Sopenharmony_ci return ret; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci break; 55862306a36Sopenharmony_ci case MEI_FOP_CONNECT: 55962306a36Sopenharmony_ci /* connect message */ 56062306a36Sopenharmony_ci ret = mei_cl_irq_connect(cl, cb, cmpl_list); 56162306a36Sopenharmony_ci if (ret) 56262306a36Sopenharmony_ci return ret; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci break; 56562306a36Sopenharmony_ci case MEI_FOP_DISCONNECT_RSP: 56662306a36Sopenharmony_ci /* send disconnect resp */ 56762306a36Sopenharmony_ci ret = mei_cl_irq_disconnect_rsp(cl, cb, cmpl_list); 56862306a36Sopenharmony_ci if (ret) 56962306a36Sopenharmony_ci return ret; 57062306a36Sopenharmony_ci break; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci case MEI_FOP_NOTIFY_START: 57362306a36Sopenharmony_ci case MEI_FOP_NOTIFY_STOP: 57462306a36Sopenharmony_ci ret = mei_cl_irq_notify(cl, cb, cmpl_list); 57562306a36Sopenharmony_ci if (ret) 57662306a36Sopenharmony_ci return ret; 57762306a36Sopenharmony_ci break; 57862306a36Sopenharmony_ci case MEI_FOP_DMA_MAP: 57962306a36Sopenharmony_ci ret = mei_cl_irq_dma_map(cl, cb, cmpl_list); 58062306a36Sopenharmony_ci if (ret) 58162306a36Sopenharmony_ci return ret; 58262306a36Sopenharmony_ci break; 58362306a36Sopenharmony_ci case MEI_FOP_DMA_UNMAP: 58462306a36Sopenharmony_ci ret = mei_cl_irq_dma_unmap(cl, cb, cmpl_list); 58562306a36Sopenharmony_ci if (ret) 58662306a36Sopenharmony_ci return ret; 58762306a36Sopenharmony_ci break; 58862306a36Sopenharmony_ci default: 58962306a36Sopenharmony_ci BUG(); 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci /* complete write list CB */ 59462306a36Sopenharmony_ci dev_dbg(dev->dev, "complete write list cb.\n"); 59562306a36Sopenharmony_ci list_for_each_entry_safe(cb, next, &dev->write_list, list) { 59662306a36Sopenharmony_ci cl = cb->cl; 59762306a36Sopenharmony_ci ret = mei_cl_irq_write(cl, cb, cmpl_list); 59862306a36Sopenharmony_ci if (ret) 59962306a36Sopenharmony_ci return ret; 60062306a36Sopenharmony_ci } 60162306a36Sopenharmony_ci return 0; 60262306a36Sopenharmony_ci} 60362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mei_irq_write_handler); 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci/** 60762306a36Sopenharmony_ci * mei_connect_timeout - connect/disconnect timeouts 60862306a36Sopenharmony_ci * 60962306a36Sopenharmony_ci * @cl: host client 61062306a36Sopenharmony_ci */ 61162306a36Sopenharmony_cistatic void mei_connect_timeout(struct mei_cl *cl) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci struct mei_device *dev = cl->dev; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci if (cl->state == MEI_FILE_CONNECTING) { 61662306a36Sopenharmony_ci if (dev->hbm_f_dot_supported) { 61762306a36Sopenharmony_ci cl->state = MEI_FILE_DISCONNECT_REQUIRED; 61862306a36Sopenharmony_ci wake_up(&cl->wait); 61962306a36Sopenharmony_ci return; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci mei_reset(dev); 62362306a36Sopenharmony_ci} 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci#define MEI_STALL_TIMER_FREQ (2 * HZ) 62662306a36Sopenharmony_ci/** 62762306a36Sopenharmony_ci * mei_schedule_stall_timer - re-arm stall_timer work 62862306a36Sopenharmony_ci * 62962306a36Sopenharmony_ci * Schedule stall timer 63062306a36Sopenharmony_ci * 63162306a36Sopenharmony_ci * @dev: the device structure 63262306a36Sopenharmony_ci */ 63362306a36Sopenharmony_civoid mei_schedule_stall_timer(struct mei_device *dev) 63462306a36Sopenharmony_ci{ 63562306a36Sopenharmony_ci schedule_delayed_work(&dev->timer_work, MEI_STALL_TIMER_FREQ); 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci/** 63962306a36Sopenharmony_ci * mei_timer - timer function. 64062306a36Sopenharmony_ci * 64162306a36Sopenharmony_ci * @work: pointer to the work_struct structure 64262306a36Sopenharmony_ci * 64362306a36Sopenharmony_ci */ 64462306a36Sopenharmony_civoid mei_timer(struct work_struct *work) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct mei_cl *cl; 64762306a36Sopenharmony_ci struct mei_device *dev = container_of(work, 64862306a36Sopenharmony_ci struct mei_device, timer_work.work); 64962306a36Sopenharmony_ci bool reschedule_timer = false; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci mutex_lock(&dev->device_lock); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci /* Catch interrupt stalls during HBM init handshake */ 65462306a36Sopenharmony_ci if (dev->dev_state == MEI_DEV_INIT_CLIENTS && 65562306a36Sopenharmony_ci dev->hbm_state != MEI_HBM_IDLE) { 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci if (dev->init_clients_timer) { 65862306a36Sopenharmony_ci if (--dev->init_clients_timer == 0) { 65962306a36Sopenharmony_ci dev_err(dev->dev, "timer: init clients timeout hbm_state = %d.\n", 66062306a36Sopenharmony_ci dev->hbm_state); 66162306a36Sopenharmony_ci mei_reset(dev); 66262306a36Sopenharmony_ci goto out; 66362306a36Sopenharmony_ci } 66462306a36Sopenharmony_ci reschedule_timer = true; 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci } 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (dev->dev_state != MEI_DEV_ENABLED) 66962306a36Sopenharmony_ci goto out; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci /*** connect/disconnect timeouts ***/ 67262306a36Sopenharmony_ci list_for_each_entry(cl, &dev->file_list, link) { 67362306a36Sopenharmony_ci if (cl->timer_count) { 67462306a36Sopenharmony_ci if (--cl->timer_count == 0) { 67562306a36Sopenharmony_ci dev_err(dev->dev, "timer: connect/disconnect timeout.\n"); 67662306a36Sopenharmony_ci mei_connect_timeout(cl); 67762306a36Sopenharmony_ci goto out; 67862306a36Sopenharmony_ci } 67962306a36Sopenharmony_ci reschedule_timer = true; 68062306a36Sopenharmony_ci } 68162306a36Sopenharmony_ci } 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ciout: 68462306a36Sopenharmony_ci if (dev->dev_state != MEI_DEV_DISABLED && reschedule_timer) 68562306a36Sopenharmony_ci mei_schedule_stall_timer(dev); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci mutex_unlock(&dev->device_lock); 68862306a36Sopenharmony_ci} 689