| .. | .. | 
|---|
 | 1 | +// SPDX-License-Identifier: GPL-2.0  | 
|---|
| 1 | 2 |  /* | 
|---|
| 2 |  | - *  | 
|---|
 | 3 | + * Copyright (c) 2003-2018, Intel Corporation. All rights reserved.  | 
|---|
| 3 | 4 |   * Intel Management Engine Interface (Intel MEI) Linux driver | 
|---|
| 4 |  | - * Copyright (c) 2003-2012, Intel Corporation.  | 
|---|
| 5 |  | - *  | 
|---|
| 6 |  | - * This program is free software; you can redistribute it and/or modify it  | 
|---|
| 7 |  | - * under the terms and conditions of the GNU General Public License,  | 
|---|
| 8 |  | - * version 2, as published by the Free Software Foundation.  | 
|---|
| 9 |  | - *  | 
|---|
| 10 |  | - * This program is distributed in the hope it will be useful, but WITHOUT  | 
|---|
| 11 |  | - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or  | 
|---|
| 12 |  | - * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for  | 
|---|
| 13 |  | - * more details.  | 
|---|
| 14 |  | - *  | 
|---|
| 15 | 5 |   */ | 
|---|
| 16 |  | -  | 
|---|
| 17 | 6 |   | 
|---|
| 18 | 7 |  #include <linux/export.h> | 
|---|
| 19 | 8 |  #include <linux/kthread.h> | 
|---|
| .. | .. | 
|---|
| 72 | 61 |   * | 
|---|
| 73 | 62 |   * @dev: mei device | 
|---|
| 74 | 63 |   * @hdr: message header | 
|---|
 | 64 | + * @discard_len: the length of the message to discard (excluding header)  | 
|---|
| 75 | 65 |   */ | 
|---|
| 76 |  | -static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)  | 
|---|
 | 66 | +static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr,  | 
|---|
 | 67 | +				size_t discard_len)  | 
|---|
| 77 | 68 |  { | 
|---|
 | 69 | +	if (hdr->dma_ring) {  | 
|---|
 | 70 | +		mei_dma_ring_read(dev, NULL,  | 
|---|
 | 71 | +				  hdr->extension[dev->rd_msg_hdr_count - 2]);  | 
|---|
 | 72 | +		discard_len = 0;  | 
|---|
 | 73 | +	}  | 
|---|
| 78 | 74 |  	/* | 
|---|
| 79 | 75 |  	 * no need to check for size as it is guarantied | 
|---|
| 80 | 76 |  	 * that length fits into rd_msg_buf | 
|---|
| 81 | 77 |  	 */ | 
|---|
| 82 |  | -	mei_read_slots(dev, dev->rd_msg_buf, hdr->length);  | 
|---|
 | 78 | +	mei_read_slots(dev, dev->rd_msg_buf, discard_len);  | 
|---|
| 83 | 79 |  	dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", | 
|---|
| 84 | 80 |  		MEI_HDR_PRM(hdr)); | 
|---|
| 85 | 81 |  } | 
|---|
| .. | .. | 
|---|
| 89 | 85 |   * | 
|---|
| 90 | 86 |   * @cl: reading client | 
|---|
| 91 | 87 |   * @mei_hdr: header of mei client message | 
|---|
 | 88 | + * @meta: extend meta header  | 
|---|
| 92 | 89 |   * @cmpl_list: completion list | 
|---|
| 93 | 90 |   * | 
|---|
| 94 | 91 |   * Return: always 0 | 
|---|
| 95 | 92 |   */ | 
|---|
| 96 | 93 |  static int mei_cl_irq_read_msg(struct mei_cl *cl, | 
|---|
| 97 | 94 |  			       struct mei_msg_hdr *mei_hdr, | 
|---|
 | 95 | +			       struct mei_ext_meta_hdr *meta,  | 
|---|
| 98 | 96 |  			       struct list_head *cmpl_list) | 
|---|
| 99 | 97 |  { | 
|---|
| 100 | 98 |  	struct mei_device *dev = cl->dev; | 
|---|
| 101 | 99 |  	struct mei_cl_cb *cb; | 
|---|
 | 100 | +  | 
|---|
| 102 | 101 |  	size_t buf_sz; | 
|---|
 | 102 | +	u32 length;  | 
|---|
 | 103 | +	int ext_len;  | 
|---|
 | 104 | +  | 
|---|
 | 105 | +	length = mei_hdr->length;  | 
|---|
 | 106 | +	ext_len = 0;  | 
|---|
 | 107 | +	if (mei_hdr->extended) {  | 
|---|
 | 108 | +		ext_len = sizeof(*meta) + mei_slots2data(meta->size);  | 
|---|
 | 109 | +		length -= ext_len;  | 
|---|
 | 110 | +	}  | 
|---|
| 103 | 111 |   | 
|---|
| 104 | 112 |  	cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list); | 
|---|
| 105 | 113 |  	if (!cb) { | 
|---|
| .. | .. | 
|---|
| 113 | 121 |  		list_add_tail(&cb->list, &cl->rd_pending); | 
|---|
| 114 | 122 |  	} | 
|---|
| 115 | 123 |   | 
|---|
 | 124 | +	if (mei_hdr->extended) {  | 
|---|
 | 125 | +		struct mei_ext_hdr *ext;  | 
|---|
 | 126 | +		struct mei_ext_hdr *vtag = NULL;  | 
|---|
 | 127 | +  | 
|---|
 | 128 | +		ext = mei_ext_begin(meta);  | 
|---|
 | 129 | +		do {  | 
|---|
 | 130 | +			switch (ext->type) {  | 
|---|
 | 131 | +			case MEI_EXT_HDR_VTAG:  | 
|---|
 | 132 | +				vtag = ext;  | 
|---|
 | 133 | +				break;  | 
|---|
 | 134 | +			case MEI_EXT_HDR_NONE:  | 
|---|
 | 135 | +				fallthrough;  | 
|---|
 | 136 | +			default:  | 
|---|
 | 137 | +				cb->status = -EPROTO;  | 
|---|
 | 138 | +				break;  | 
|---|
 | 139 | +			}  | 
|---|
 | 140 | +  | 
|---|
 | 141 | +			ext = mei_ext_next(ext);  | 
|---|
 | 142 | +		} while (!mei_ext_last(meta, ext));  | 
|---|
 | 143 | +  | 
|---|
 | 144 | +		if (!vtag) {  | 
|---|
 | 145 | +			cl_dbg(dev, cl, "vtag not found in extended header.\n");  | 
|---|
 | 146 | +			cb->status = -EPROTO;  | 
|---|
 | 147 | +			goto discard;  | 
|---|
 | 148 | +		}  | 
|---|
 | 149 | +  | 
|---|
 | 150 | +		cl_dbg(dev, cl, "vtag: %d\n", vtag->ext_payload[0]);  | 
|---|
 | 151 | +		if (cb->vtag && cb->vtag != vtag->ext_payload[0]) {  | 
|---|
 | 152 | +			cl_err(dev, cl, "mismatched tag: %d != %d\n",  | 
|---|
 | 153 | +			       cb->vtag, vtag->ext_payload[0]);  | 
|---|
 | 154 | +			cb->status = -EPROTO;  | 
|---|
 | 155 | +			goto discard;  | 
|---|
 | 156 | +		}  | 
|---|
 | 157 | +		cb->vtag = vtag->ext_payload[0];  | 
|---|
 | 158 | +	}  | 
|---|
 | 159 | +  | 
|---|
| 116 | 160 |  	if (!mei_cl_is_connected(cl)) { | 
|---|
| 117 | 161 |  		cl_dbg(dev, cl, "not connected\n"); | 
|---|
| 118 | 162 |  		cb->status = -ENODEV; | 
|---|
| 119 | 163 |  		goto discard; | 
|---|
| 120 | 164 |  	} | 
|---|
| 121 | 165 |   | 
|---|
| 122 |  | -	buf_sz = mei_hdr->length + cb->buf_idx;  | 
|---|
 | 166 | +	if (mei_hdr->dma_ring)  | 
|---|
 | 167 | +		length = mei_hdr->extension[mei_data2slots(ext_len)];  | 
|---|
 | 168 | +  | 
|---|
 | 169 | +	buf_sz = length + cb->buf_idx;  | 
|---|
| 123 | 170 |  	/* catch for integer overflow */ | 
|---|
| 124 | 171 |  	if (buf_sz < cb->buf_idx) { | 
|---|
| 125 | 172 |  		cl_err(dev, cl, "message is too big len %d idx %zu\n", | 
|---|
| 126 |  | -		       mei_hdr->length, cb->buf_idx);  | 
|---|
 | 173 | +		       length, cb->buf_idx);  | 
|---|
| 127 | 174 |  		cb->status = -EMSGSIZE; | 
|---|
| 128 | 175 |  		goto discard; | 
|---|
| 129 | 176 |  	} | 
|---|
| 130 | 177 |   | 
|---|
| 131 | 178 |  	if (cb->buf.size < buf_sz) { | 
|---|
| 132 | 179 |  		cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n", | 
|---|
| 133 |  | -			cb->buf.size, mei_hdr->length, cb->buf_idx);  | 
|---|
 | 180 | +			cb->buf.size, length, cb->buf_idx);  | 
|---|
| 134 | 181 |  		cb->status = -EMSGSIZE; | 
|---|
| 135 | 182 |  		goto discard; | 
|---|
| 136 | 183 |  	} | 
|---|
| 137 | 184 |   | 
|---|
| 138 |  | -	mei_read_slots(dev, cb->buf.data + cb->buf_idx, mei_hdr->length);  | 
|---|
 | 185 | +	if (mei_hdr->dma_ring) {  | 
|---|
 | 186 | +		mei_dma_ring_read(dev, cb->buf.data + cb->buf_idx, length);  | 
|---|
 | 187 | +		/*  for DMA read 0 length to generate interrupt to the device */  | 
|---|
 | 188 | +		mei_read_slots(dev, cb->buf.data + cb->buf_idx, 0);  | 
|---|
 | 189 | +	} else {  | 
|---|
 | 190 | +		mei_read_slots(dev, cb->buf.data + cb->buf_idx, length);  | 
|---|
 | 191 | +	}  | 
|---|
| 139 | 192 |   | 
|---|
| 140 |  | -	cb->buf_idx += mei_hdr->length;  | 
|---|
 | 193 | +	cb->buf_idx += length;  | 
|---|
| 141 | 194 |   | 
|---|
| 142 | 195 |  	if (mei_hdr->msg_complete) { | 
|---|
| 143 | 196 |  		cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx); | 
|---|
| .. | .. | 
|---|
| 152 | 205 |  discard: | 
|---|
| 153 | 206 |  	if (cb) | 
|---|
| 154 | 207 |  		list_move_tail(&cb->list, cmpl_list); | 
|---|
| 155 |  | -	mei_irq_discard_msg(dev, mei_hdr);  | 
|---|
 | 208 | +	mei_irq_discard_msg(dev, mei_hdr, length);  | 
|---|
| 156 | 209 |  	return 0; | 
|---|
| 157 | 210 |  } | 
|---|
| 158 | 211 |   | 
|---|
| .. | .. | 
|---|
| 245 | 298 |  static inline int hdr_is_valid(u32 msg_hdr) | 
|---|
| 246 | 299 |  { | 
|---|
| 247 | 300 |  	struct mei_msg_hdr *mei_hdr; | 
|---|
 | 301 | +	u32 expected_len = 0;  | 
|---|
| 248 | 302 |   | 
|---|
| 249 | 303 |  	mei_hdr = (struct mei_msg_hdr *)&msg_hdr; | 
|---|
| 250 | 304 |  	if (!msg_hdr || mei_hdr->reserved) | 
|---|
 | 305 | +		return -EBADMSG;  | 
|---|
 | 306 | +  | 
|---|
 | 307 | +	if (mei_hdr->dma_ring)  | 
|---|
 | 308 | +		expected_len += MEI_SLOT_SIZE;  | 
|---|
 | 309 | +	if (mei_hdr->extended)  | 
|---|
 | 310 | +		expected_len += MEI_SLOT_SIZE;  | 
|---|
 | 311 | +	if (mei_hdr->length < expected_len)  | 
|---|
| 251 | 312 |  		return -EBADMSG; | 
|---|
| 252 | 313 |   | 
|---|
| 253 | 314 |  	return 0; | 
|---|
| .. | .. | 
|---|
| 267 | 328 |  			 struct list_head *cmpl_list, s32 *slots) | 
|---|
| 268 | 329 |  { | 
|---|
| 269 | 330 |  	struct mei_msg_hdr *mei_hdr; | 
|---|
 | 331 | +	struct mei_ext_meta_hdr *meta_hdr = NULL;  | 
|---|
| 270 | 332 |  	struct mei_cl *cl; | 
|---|
| 271 | 333 |  	int ret; | 
|---|
 | 334 | +	u32 ext_meta_hdr_u32;  | 
|---|
 | 335 | +	u32 hdr_size_left;  | 
|---|
 | 336 | +	u32 hdr_size_ext;  | 
|---|
 | 337 | +	int i;  | 
|---|
 | 338 | +	int ext_hdr_end;  | 
|---|
| 272 | 339 |   | 
|---|
| 273 |  | -	if (!dev->rd_msg_hdr) {  | 
|---|
| 274 |  | -		dev->rd_msg_hdr = mei_read_hdr(dev);  | 
|---|
 | 340 | +	if (!dev->rd_msg_hdr[0]) {  | 
|---|
 | 341 | +		dev->rd_msg_hdr[0] = mei_read_hdr(dev);  | 
|---|
 | 342 | +		dev->rd_msg_hdr_count = 1;  | 
|---|
| 275 | 343 |  		(*slots)--; | 
|---|
| 276 | 344 |  		dev_dbg(dev->dev, "slots =%08x.\n", *slots); | 
|---|
| 277 | 345 |   | 
|---|
| 278 |  | -		ret = hdr_is_valid(dev->rd_msg_hdr);  | 
|---|
 | 346 | +		ret = hdr_is_valid(dev->rd_msg_hdr[0]);  | 
|---|
| 279 | 347 |  		if (ret) { | 
|---|
| 280 | 348 |  			dev_err(dev->dev, "corrupted message header 0x%08X\n", | 
|---|
| 281 |  | -				dev->rd_msg_hdr);  | 
|---|
 | 349 | +				dev->rd_msg_hdr[0]);  | 
|---|
| 282 | 350 |  			goto end; | 
|---|
| 283 | 351 |  		} | 
|---|
| 284 | 352 |  	} | 
|---|
| 285 | 353 |   | 
|---|
| 286 |  | -	mei_hdr = (struct mei_msg_hdr *)&dev->rd_msg_hdr;  | 
|---|
 | 354 | +	mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr;  | 
|---|
| 287 | 355 |  	dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); | 
|---|
| 288 | 356 |   | 
|---|
| 289 | 357 |  	if (mei_slots2data(*slots) < mei_hdr->length) { | 
|---|
| .. | .. | 
|---|
| 292 | 360 |  		/* we can't read the message */ | 
|---|
| 293 | 361 |  		ret = -ENODATA; | 
|---|
| 294 | 362 |  		goto end; | 
|---|
 | 363 | +	}  | 
|---|
 | 364 | +  | 
|---|
 | 365 | +	ext_hdr_end = 1;  | 
|---|
 | 366 | +	hdr_size_left = mei_hdr->length;  | 
|---|
 | 367 | +  | 
|---|
 | 368 | +	if (mei_hdr->extended) {  | 
|---|
 | 369 | +		if (!dev->rd_msg_hdr[1]) {  | 
|---|
 | 370 | +			ext_meta_hdr_u32 = mei_read_hdr(dev);  | 
|---|
 | 371 | +			dev->rd_msg_hdr[1] = ext_meta_hdr_u32;  | 
|---|
 | 372 | +			dev->rd_msg_hdr_count++;  | 
|---|
 | 373 | +			(*slots)--;  | 
|---|
 | 374 | +			dev_dbg(dev->dev, "extended header is %08x\n",  | 
|---|
 | 375 | +				ext_meta_hdr_u32);  | 
|---|
 | 376 | +		}  | 
|---|
 | 377 | +		meta_hdr = ((struct mei_ext_meta_hdr *)dev->rd_msg_hdr + 1);  | 
|---|
 | 378 | +		if (check_add_overflow((u32)sizeof(*meta_hdr),  | 
|---|
 | 379 | +				       mei_slots2data(meta_hdr->size),  | 
|---|
 | 380 | +				       &hdr_size_ext)) {  | 
|---|
 | 381 | +			dev_err(dev->dev, "extended message size too big %d\n",  | 
|---|
 | 382 | +				meta_hdr->size);  | 
|---|
 | 383 | +			return -EBADMSG;  | 
|---|
 | 384 | +		}  | 
|---|
 | 385 | +		if (hdr_size_left < hdr_size_ext) {  | 
|---|
 | 386 | +			dev_err(dev->dev, "corrupted message header len %d\n",  | 
|---|
 | 387 | +				mei_hdr->length);  | 
|---|
 | 388 | +			return -EBADMSG;  | 
|---|
 | 389 | +		}  | 
|---|
 | 390 | +		hdr_size_left -= hdr_size_ext;  | 
|---|
 | 391 | +  | 
|---|
 | 392 | +		ext_hdr_end = meta_hdr->size + 2;  | 
|---|
 | 393 | +		for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) {  | 
|---|
 | 394 | +			dev->rd_msg_hdr[i] = mei_read_hdr(dev);  | 
|---|
 | 395 | +			dev_dbg(dev->dev, "extended header %d is %08x\n", i,  | 
|---|
 | 396 | +				dev->rd_msg_hdr[i]);  | 
|---|
 | 397 | +			dev->rd_msg_hdr_count++;  | 
|---|
 | 398 | +			(*slots)--;  | 
|---|
 | 399 | +		}  | 
|---|
 | 400 | +	}  | 
|---|
 | 401 | +  | 
|---|
 | 402 | +	if (mei_hdr->dma_ring) {  | 
|---|
 | 403 | +		if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) {  | 
|---|
 | 404 | +			dev_err(dev->dev, "corrupted message header len %d\n",  | 
|---|
 | 405 | +				mei_hdr->length);  | 
|---|
 | 406 | +			return -EBADMSG;  | 
|---|
 | 407 | +		}  | 
|---|
 | 408 | +  | 
|---|
 | 409 | +		dev->rd_msg_hdr[ext_hdr_end] = mei_read_hdr(dev);  | 
|---|
 | 410 | +		dev->rd_msg_hdr_count++;  | 
|---|
 | 411 | +		(*slots)--;  | 
|---|
 | 412 | +		mei_hdr->length -= sizeof(dev->rd_msg_hdr[ext_hdr_end]);  | 
|---|
| 295 | 413 |  	} | 
|---|
| 296 | 414 |   | 
|---|
| 297 | 415 |  	/*  HBM message */ | 
|---|
| .. | .. | 
|---|
| 309 | 427 |  	list_for_each_entry(cl, &dev->file_list, link) { | 
|---|
| 310 | 428 |  		if (mei_cl_hbm_equal(cl, mei_hdr)) { | 
|---|
| 311 | 429 |  			cl_dbg(dev, cl, "got a message\n"); | 
|---|
| 312 |  | -			break;  | 
|---|
 | 430 | +			ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list);  | 
|---|
 | 431 | +			goto reset_slots;  | 
|---|
| 313 | 432 |  		} | 
|---|
| 314 | 433 |  	} | 
|---|
| 315 | 434 |   | 
|---|
| 316 | 435 |  	/* if no recipient cl was found we assume corrupted header */ | 
|---|
| 317 |  | -	if (&cl->link == &dev->file_list) {  | 
|---|
| 318 |  | -		/* A message for not connected fixed address clients  | 
|---|
| 319 |  | -		 * should be silently discarded  | 
|---|
| 320 |  | -		 * On power down client may be force cleaned,  | 
|---|
| 321 |  | -		 * silently discard such messages  | 
|---|
| 322 |  | -		 */  | 
|---|
| 323 |  | -		if (hdr_is_fixed(mei_hdr) ||  | 
|---|
| 324 |  | -		    dev->dev_state == MEI_DEV_POWER_DOWN) {  | 
|---|
| 325 |  | -			mei_irq_discard_msg(dev, mei_hdr);  | 
|---|
| 326 |  | -			ret = 0;  | 
|---|
| 327 |  | -			goto reset_slots;  | 
|---|
| 328 |  | -		}  | 
|---|
| 329 |  | -		dev_err(dev->dev, "no destination client found 0x%08X\n",  | 
|---|
| 330 |  | -				dev->rd_msg_hdr);  | 
|---|
| 331 |  | -		ret = -EBADMSG;  | 
|---|
| 332 |  | -		goto end;  | 
|---|
 | 436 | +	/* A message for not connected fixed address clients  | 
|---|
 | 437 | +	 * should be silently discarded  | 
|---|
 | 438 | +	 * On power down client may be force cleaned,  | 
|---|
 | 439 | +	 * silently discard such messages  | 
|---|
 | 440 | +	 */  | 
|---|
 | 441 | +	if (hdr_is_fixed(mei_hdr) ||  | 
|---|
 | 442 | +	    dev->dev_state == MEI_DEV_POWER_DOWN) {  | 
|---|
 | 443 | +		mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length);  | 
|---|
 | 444 | +		ret = 0;  | 
|---|
 | 445 | +		goto reset_slots;  | 
|---|
| 333 | 446 |  	} | 
|---|
| 334 |  | -  | 
|---|
| 335 |  | -	ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list);  | 
|---|
| 336 |  | -  | 
|---|
 | 447 | +	dev_err(dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]);  | 
|---|
 | 448 | +	ret = -EBADMSG;  | 
|---|
 | 449 | +	goto end;  | 
|---|
| 337 | 450 |   | 
|---|
| 338 | 451 |  reset_slots: | 
|---|
| 339 | 452 |  	/* reset the number of slots and header */ | 
|---|
 | 453 | +	memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr));  | 
|---|
 | 454 | +	dev->rd_msg_hdr_count = 0;  | 
|---|
| 340 | 455 |  	*slots = mei_count_full_read_slots(dev); | 
|---|
| 341 |  | -	dev->rd_msg_hdr = 0;  | 
|---|
| 342 |  | -  | 
|---|
| 343 | 456 |  	if (*slots == -EOVERFLOW) { | 
|---|
| 344 | 457 |  		/* overflow - reset */ | 
|---|
| 345 | 458 |  		dev_err(dev->dev, "resetting due to slots overflow.\n"); | 
|---|