685 lines · 15.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Copyright (c) 2003-2018, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include <linux/export.h>#include <linux/kthread.h>#include <linux/interrupt.h>#include <linux/fs.h>#include <linux/jiffies.h>#include <linux/slab.h>#include <linux/pm_runtime.h> #include <linux/mei.h> #include "mei_dev.h"#include "hbm.h"#include "client.h"  /** * mei_irq_compl_handler - dispatch complete handlers *	for the completed callbacks * * @dev: mei device * @cmpl_list: list of completed cbs */void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list){	struct mei_cl_cb *cb, *next;	struct mei_cl *cl; 	list_for_each_entry_safe(cb, next, cmpl_list, list) {		cl = cb->cl;		list_del_init(&cb->list); 		cl_dbg(dev, cl, "completing call back.\n");		mei_cl_complete(cl, cb);	}}EXPORT_SYMBOL_GPL(mei_irq_compl_handler); /** * mei_cl_hbm_equal - check if hbm is addressed to the client * * @cl: host client * @mei_hdr: header of mei client message * * Return: true if matches, false otherwise */static inline int mei_cl_hbm_equal(struct mei_cl *cl,			struct mei_msg_hdr *mei_hdr){	return  mei_cl_host_addr(cl) == mei_hdr->host_addr &&		mei_cl_me_id(cl) == mei_hdr->me_addr;} /** * mei_irq_discard_msg  - discard received message * * @dev: mei device * @hdr: message header * @discard_len: the length of the message to discard (excluding header) */static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr,				size_t discard_len){	if (hdr->dma_ring) {		mei_dma_ring_read(dev, NULL,				  hdr->extension[dev->rd_msg_hdr_count - 2]);		discard_len = 0;	}	/*	 * no need to check for size as it is guaranteed	 * that length fits into rd_msg_buf	 */	mei_read_slots(dev, dev->rd_msg_buf, discard_len);	dev_dbg(&dev->dev, "discarding message " MEI_HDR_FMT "\n",		MEI_HDR_PRM(hdr));} /** * mei_cl_irq_read_msg - process client message * * @cl: reading client * @mei_hdr: header of mei client message * @meta: extend meta header * @cmpl_list: completion list * * Return: always 0 */static int mei_cl_irq_read_msg(struct mei_cl *cl,			       struct mei_msg_hdr *mei_hdr,			       struct mei_ext_meta_hdr *meta,			       struct list_head *cmpl_list){	struct mei_device *dev = cl->dev;	struct mei_cl_cb *cb; 	struct mei_ext_hdr_vtag *vtag_hdr = NULL;	struct mei_ext_hdr_gsc_f2h *gsc_f2h = NULL; 	size_t buf_sz;	u32 length;	u32 ext_len; 	length = mei_hdr->length;	ext_len = 0;	if (mei_hdr->extended) {		ext_len = sizeof(*meta) + mei_slots2data(meta->size);		length -= ext_len;	} 	cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);	if (!cb) {		if (!mei_cl_is_fixed_address(cl)) {			cl_err(dev, cl, "pending read cb not found\n");			goto discard;		}		cb = mei_cl_alloc_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, cl->fp);		if (!cb)			goto discard;		list_add_tail(&cb->list, &cl->rd_pending);	} 	if (mei_hdr->extended) {		struct mei_ext_hdr *ext = mei_ext_begin(meta);		do {			switch (ext->type) {			case MEI_EXT_HDR_VTAG:				vtag_hdr = (struct mei_ext_hdr_vtag *)ext;				break;			case MEI_EXT_HDR_GSC:				gsc_f2h = (struct mei_ext_hdr_gsc_f2h *)ext;				cb->ext_hdr = (struct mei_ext_hdr *) kzalloc_obj(*gsc_f2h);				if (!cb->ext_hdr) {					cb->status = -ENOMEM;					goto discard;				}				break;			case MEI_EXT_HDR_NONE:				fallthrough;			default:				cl_err(dev, cl, "unknown extended header\n");				cb->status = -EPROTO;				break;			} 			ext = mei_ext_next(ext);		} while (!mei_ext_last(meta, ext)); 		if (!vtag_hdr && !gsc_f2h) {			cl_dbg(dev, cl, "no vtag or gsc found in extended header.\n");			cb->status = -EPROTO;			goto discard;		}	} 	if (vtag_hdr) {		cl_dbg(dev, cl, "vtag: %d\n", vtag_hdr->vtag);		if (cb->vtag && cb->vtag != vtag_hdr->vtag) {			cl_err(dev, cl, "mismatched tag: %d != %d\n",			       cb->vtag, vtag_hdr->vtag);			cb->status = -EPROTO;			goto discard;		}		cb->vtag = vtag_hdr->vtag;	} 	if (gsc_f2h) {		u32 ext_hdr_len = mei_ext_hdr_len(&gsc_f2h->hdr); 		if (!dev->hbm_f_gsc_supported) {			cl_err(dev, cl, "gsc extended header is not supported\n");			cb->status = -EPROTO;			goto discard;		} 		if (length) {			cl_err(dev, cl, "no data allowed in cb with gsc\n");			cb->status = -EPROTO;			goto discard;		}		if (ext_hdr_len > sizeof(*gsc_f2h)) {			cl_err(dev, cl, "gsc extended header is too big %u\n", ext_hdr_len);			cb->status = -EPROTO;			goto discard;		}		memcpy(cb->ext_hdr, gsc_f2h, ext_hdr_len);	} 	if (!mei_cl_is_connected(cl)) {		cl_dbg(dev, cl, "not connected\n");		cb->status = -ENODEV;		goto discard;	} 	if (mei_hdr->dma_ring)		length = mei_hdr->extension[mei_data2slots(ext_len)]; 	buf_sz = length + cb->buf_idx;	/* catch for integer overflow */	if (buf_sz < cb->buf_idx) {		cl_err(dev, cl, "message is too big len %d idx %zu\n",		       length, cb->buf_idx);		cb->status = -EMSGSIZE;		goto discard;	} 	if (cb->buf.size < buf_sz) {		cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n",			cb->buf.size, length, cb->buf_idx);		cb->status = -EMSGSIZE;		goto discard;	} 	if (mei_hdr->dma_ring) {		mei_dma_ring_read(dev, cb->buf.data + cb->buf_idx, length);		/*  for DMA read 0 length to generate interrupt to the device */		mei_read_slots(dev, cb->buf.data + cb->buf_idx, 0);	} else {		mei_read_slots(dev, cb->buf.data + cb->buf_idx, length);	} 	cb->buf_idx += length; 	if (mei_hdr->msg_complete) {		cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);		list_move_tail(&cb->list, cmpl_list);	} else {		pm_request_autosuspend(dev->parent);	} 	return 0; discard:	if (cb)		list_move_tail(&cb->list, cmpl_list);	mei_irq_discard_msg(dev, mei_hdr, length);	return 0;} /** * mei_cl_irq_disconnect_rsp - send disconnection response message * * @cl: client * @cb: callback block. * @cmpl_list: complete list. * * Return: 0, OK; otherwise, error. */static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,				     struct list_head *cmpl_list){	struct mei_device *dev = cl->dev;	u32 msg_slots;	int slots;	int ret; 	msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_response));	slots = mei_hbuf_empty_slots(dev);	if (slots < 0)		return -EOVERFLOW; 	if ((u32)slots < msg_slots)		return -EMSGSIZE; 	ret = mei_hbm_cl_disconnect_rsp(dev, cl);	list_move_tail(&cb->list, cmpl_list); 	return ret;} /** * mei_cl_irq_read - processes client read related operation from the *	interrupt thread context - request for flow control credits * * @cl: client * @cb: callback block. * @cmpl_list: complete list. * * Return: 0, OK; otherwise, error. */static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,			   struct list_head *cmpl_list){	struct mei_device *dev = cl->dev;	u32 msg_slots;	int slots;	int ret; 	if (!list_empty(&cl->rd_pending))		return 0; 	msg_slots = mei_hbm2slots(sizeof(struct hbm_flow_control));	slots = mei_hbuf_empty_slots(dev);	if (slots < 0)		return -EOVERFLOW; 	if ((u32)slots < msg_slots)		return -EMSGSIZE; 	ret = mei_hbm_cl_flow_control_req(dev, cl);	if (ret) {		cl->status = ret;		cb->buf_idx = 0;		list_move_tail(&cb->list, cmpl_list);		return ret;	} 	pm_request_autosuspend(dev->parent); 	list_move_tail(&cb->list, &cl->rd_pending); 	return 0;} static inline bool hdr_is_hbm(struct mei_msg_hdr *mei_hdr){	return mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0;} static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr){	return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0;} static inline int hdr_is_valid(u32 msg_hdr){	struct mei_msg_hdr *mei_hdr;	u32 expected_len = 0; 	mei_hdr = (struct mei_msg_hdr *)&msg_hdr;	if (!msg_hdr || mei_hdr->reserved)		return -EBADMSG; 	if (mei_hdr->dma_ring)		expected_len += MEI_SLOT_SIZE;	if (mei_hdr->extended)		expected_len += MEI_SLOT_SIZE;	if (mei_hdr->length < expected_len)		return -EBADMSG; 	return 0;} /** * mei_irq_read_handler - bottom half read routine after ISR to * handle the read processing. * * @dev: the device structure * @cmpl_list: An instance of our list structure * @slots: slots to read. * * Return: 0 on success, <0 on failure. */int mei_irq_read_handler(struct mei_device *dev,			 struct list_head *cmpl_list, s32 *slots){	struct mei_msg_hdr *mei_hdr;	struct mei_ext_meta_hdr *meta_hdr = NULL;	struct mei_cl *cl;	int ret;	u32 hdr_size_left;	u32 hdr_size_ext;	int i;	int ext_hdr_end; 	if (!dev->rd_msg_hdr[0]) {		dev->rd_msg_hdr[0] = mei_read_hdr(dev);		dev->rd_msg_hdr_count = 1;		(*slots)--;		dev_dbg(&dev->dev, "slots =%08x.\n", *slots); 		ret = hdr_is_valid(dev->rd_msg_hdr[0]);		if (ret) {			dev_err(&dev->dev, "corrupted message header 0x%08X\n",				dev->rd_msg_hdr[0]);			goto end;		}	} 	mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr;	dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); 	if (mei_slots2data(*slots) < mei_hdr->length) {		dev_err(&dev->dev, "less data available than length=%08x.\n",				*slots);		/* we can't read the message */		ret = -ENODATA;		goto end;	} 	ext_hdr_end = 1;	hdr_size_left = mei_hdr->length; 	if (mei_hdr->extended) {		if (!dev->rd_msg_hdr[1]) {			dev->rd_msg_hdr[1] = mei_read_hdr(dev);			dev->rd_msg_hdr_count++;			(*slots)--;			dev_dbg(&dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]);		}		meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]);		if (check_add_overflow((u32)sizeof(*meta_hdr),				       mei_slots2data(meta_hdr->size),				       &hdr_size_ext)) {			dev_err(&dev->dev, "extended message size too big %d\n",				meta_hdr->size);			return -EBADMSG;		}		if (hdr_size_left < hdr_size_ext) {			dev_err(&dev->dev, "corrupted message header len %d\n",				mei_hdr->length);			return -EBADMSG;		}		hdr_size_left -= hdr_size_ext; 		ext_hdr_end = meta_hdr->size + 2;		for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) {			dev->rd_msg_hdr[i] = mei_read_hdr(dev);			dev_dbg(&dev->dev, "extended header %d is %08x\n", i,				dev->rd_msg_hdr[i]);			dev->rd_msg_hdr_count++;			(*slots)--;		}	} 	if (mei_hdr->dma_ring) {		if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) {			dev_err(&dev->dev, "corrupted message header len %d\n",				mei_hdr->length);			return -EBADMSG;		} 		dev->rd_msg_hdr[ext_hdr_end] = mei_read_hdr(dev);		dev->rd_msg_hdr_count++;		(*slots)--;		mei_hdr->length -= sizeof(dev->rd_msg_hdr[ext_hdr_end]);	} 	/*  HBM message */	if (hdr_is_hbm(mei_hdr)) {		ret = mei_hbm_dispatch(dev, mei_hdr);		if (ret) {			dev_dbg(&dev->dev, "mei_hbm_dispatch failed ret = %d\n", ret);			goto end;		}		goto reset_slots;	} 	/* find recipient cl */	list_for_each_entry(cl, &dev->file_list, link) {		if (mei_cl_hbm_equal(cl, mei_hdr)) {			cl_dbg(dev, cl, "got a message\n");			ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list);			goto reset_slots;		}	} 	/* if no recipient cl was found we assume corrupted header */	/* A message for not connected fixed address clients	 * should be silently discarded	 * On power down client may be force cleaned,	 * silently discard such messages	 */	if (hdr_is_fixed(mei_hdr) ||	    dev->dev_state == MEI_DEV_POWER_DOWN) {		mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length);		ret = 0;		goto reset_slots;	}	dev_err(&dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]);	ret = -EBADMSG;	goto end; reset_slots:	/* reset the number of slots and header */	memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr));	dev->rd_msg_hdr_count = 0;	*slots = mei_count_full_read_slots(dev);	if (*slots == -EOVERFLOW) {		/* overflow - reset */		dev_err(&dev->dev, "resetting due to slots overflow.\n");		/* set the event since message has been read */		ret = -ERANGE;		goto end;	}end:	return ret;}EXPORT_SYMBOL_GPL(mei_irq_read_handler);  /** * mei_irq_write_handler -  dispatch write requests *  after irq received * * @dev: the device structure

Lines 1–500 of 685.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t