1 /* 2 * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 * SPDX-License-Identifier: MIT 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a 6 * copy of this software and associated documentation files (the "Software"), 7 * to deal in the Software without restriction, including without limitation 8 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 * and/or sell copies of the Software, and to permit persons to whom the 10 * Software is furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 */ 23 24 #include "common_nvswitch.h" 25 #include "haldef_nvswitch.h" 26 #include "fsprpc_nvswitch.h" 27 28 #include "fsp/nvdm_payload_cmd_response.h" 29 #include "fsp/fsp_nvdm_format.h" 30 31 /*! 32 * @brief Check if FSP RM command queue is empty 33 * 34 * @param[in] device nvswitch device pointer 35 * 36 * @return NV_TRUE if queue is empty, NV_FALSE otherwise 37 */ 38 static NvBool 39 _nvswitch_fsp_is_queue_empty 40 ( 41 nvswitch_device *device 42 ) 43 { 44 NvU32 cmdqHead, cmdqTail; 45 46 nvswitch_fsp_get_cmdq_head_tail(device, &cmdqHead, &cmdqTail); 47 48 // FSP will set QUEUE_HEAD = TAIL after each packet is received 49 return (cmdqHead == cmdqTail); 50 } 51 52 /*! 53 * @brief Check if FSP RM message queue is empty 54 * 55 * @param[in] device nvswitch_device pointer 56 * 57 * @return NV_TRUE if queue is empty, NV_FALSE otherwise 58 */ 59 static NvBool 60 _nvswitch_fsp_is_msgq_empty 61 ( 62 nvswitch_device *device 63 ) 64 { 65 NvU32 msgqHead, msgqTail; 66 67 nvswitch_fsp_get_msgq_head_tail(device, &msgqHead, &msgqTail); 68 return (msgqHead == msgqTail); 69 } 70 71 /*! 72 * @brief Wait for FSP RM command queue to be empty 73 * 74 * @param[in] device nvswitch_device pointer 75 * 76 * @return NVL_SUCCESS, or NV_ERR_TIMEOUT 77 */ 78 static NvlStatus 79 _nvswitch_fsp_poll_for_queue_empty 80 ( 81 nvswitch_device *device 82 ) 83 { 84 NvBool bKeepPolling; 85 NvBool bMsgqEmpty; 86 NvBool bCmdqEmpty; 87 NVSWITCH_TIMEOUT timeout; 88 89 nvswitch_timeout_create(10 * NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout); 90 91 do 92 { 93 bKeepPolling = nvswitch_timeout_check(&timeout) ? NV_FALSE : NV_TRUE; 94 95 bMsgqEmpty = _nvswitch_fsp_is_msgq_empty(device); 96 bCmdqEmpty = _nvswitch_fsp_is_queue_empty(device); 97 98 // 99 // For now we assume that any response from FSP before sending a command 100 // indicates an error and we should abort. 101 // 102 if (!bCmdqEmpty && !bMsgqEmpty) 103 { 104 nvswitch_fsp_read_message(device, NULL, 0); 105 NVSWITCH_PRINT(device, ERROR, "Received error message from FSP while waiting for CMDQ to be empty.\n"); 106 return -NVL_ERR_GENERIC; 107 } 108 109 if (bCmdqEmpty) 110 { 111 break; 112 } 113 114 if (!bKeepPolling) 115 { 116 NVSWITCH_PRINT(device, ERROR, "Timed out waiting for FSP command queue to be empty.\n"); 117 return -NVL_ERR_GENERIC; 118 } 119 120 nvswitch_os_sleep(1); 121 } 122 while(bKeepPolling); 123 124 return NVL_SUCCESS; 125 } 126 127 /*! 128 * @brief Poll for response from FSP via RM message queue 129 * 130 * @param[in] device nvswitch_device pointer 131 * 132 * @return NVL_SUCCESS, or NV_ERR_TIMEOUT 133 */ 134 static NvlStatus 135 _nvswitch_fsp_poll_for_response 136 ( 137 nvswitch_device *device 138 ) 139 { 140 NvBool bKeepPolling; 141 NVSWITCH_TIMEOUT timeout; 142 143 nvswitch_timeout_create(10 * NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout); 144 145 do 146 { 147 bKeepPolling = nvswitch_timeout_check(&timeout) ? NV_FALSE : NV_TRUE; 148 149 // 150 // Poll for message queue to wait for FSP's reply 151 // 152 if (!_nvswitch_fsp_is_msgq_empty(device)) 153 { 154 break; 155 } 156 157 if (!bKeepPolling) 158 { 159 NVSWITCH_PRINT(device, ERROR, "FSP command timed out.\n"); 160 return -NVL_ERR_GENERIC; 161 } 162 163 nvswitch_os_sleep(1); 164 } 165 while(bKeepPolling); 166 167 return NVL_SUCCESS; 168 } 169 170 /*! 171 * @brief Read and process message from FSP via RM message queue. 172 * 173 * Supports both single and multi-packet message. For multi-packet messages, this 174 * loops until all packets are received, polling at each iteration for the next 175 * packet to come in. If a buffer is provided, the message payload will be 176 * returned there. 177 * 178 * @note: For multi-packet messages, a buffer in which the message payload will 179 * be reconstructed must be provided. 180 * 181 * @param[in] device nvswitch_device pointer 182 * @param[in/out] pPayloadBuffer Buffer in which to return message payload 183 * @param[in] payloadBufferSize Payload buffer size 184 * 185 * @return NVL_SUCCESS, NV_ERR_INVALID_DATA, NV_ERR_INSUFFICIENT_RESOURCES, or errors 186 * from functions called within 187 */ 188 NvlStatus 189 nvswitch_fsp_read_message 190 ( 191 nvswitch_device *device, 192 NvU8 *pPayloadBuffer, 193 NvU32 payloadBufferSize 194 ) 195 { 196 NvU8 *pPacketBuffer; 197 NvlStatus status; 198 NvU32 totalPayloadSize = 0; 199 NvU8 *pMessagePayload; 200 NvU8 packetState = MCTP_PACKET_STATE_START; 201 202 if (_nvswitch_fsp_is_msgq_empty(device)) 203 { 204 NVSWITCH_PRINT(device, WARN, "Tried to read FSP response but MSG queue is empty\n"); 205 return NVL_SUCCESS; 206 } 207 208 pPacketBuffer = nvswitch_os_malloc(nvswitch_fsp_get_channel_size(device)); 209 if (pPacketBuffer == NULL) 210 { 211 NVSWITCH_PRINT(device, ERROR, 212 "Failed to allocate memory for GLT!!\n"); 213 return -NVL_NO_MEM; 214 } 215 216 while ((packetState != MCTP_PACKET_STATE_END) && (packetState != MCTP_PACKET_STATE_SINGLE_PACKET)) 217 { 218 NvU32 msgqHead, msgqTail; 219 NvU32 packetSize; 220 NvU32 curPayloadSize; 221 NvU8 curHeaderSize; 222 NvU8 tag; 223 224 // Wait for next packet 225 status = _nvswitch_fsp_poll_for_response(device); 226 if (status != NVL_SUCCESS) 227 { 228 goto done; 229 } 230 231 nvswitch_fsp_get_msgq_head_tail(device, &msgqHead, &msgqTail); 232 233 // Tail points to last DWORD in packet, not DWORD immediately following it 234 packetSize = (msgqTail - msgqHead) + sizeof(NvU32); 235 236 if ((packetSize < sizeof(NvU32)) || 237 (packetSize > nvswitch_fsp_get_channel_size(device))) 238 { 239 NVSWITCH_PRINT(device, ERROR, "FSP response packet is invalid size: size=0x%x bytes\n", packetSize); 240 status = -NVL_ERR_INVALID_STATE; 241 goto done; 242 } 243 244 nvswitch_fsp_read_from_emem(device, pPacketBuffer, packetSize); 245 246 status = nvswitch_fsp_get_packet_info(device, pPacketBuffer, packetSize, &packetState, &tag); 247 if (status != NVL_SUCCESS) 248 { 249 goto done; 250 } 251 252 if ((packetState == MCTP_PACKET_STATE_START) || (packetState == MCTP_PACKET_STATE_SINGLE_PACKET)) 253 { 254 // Packet contains payload header 255 curHeaderSize = sizeof(MCTP_HEADER); 256 } 257 else 258 { 259 curHeaderSize = sizeof(NvU32); 260 } 261 262 curPayloadSize = packetSize - curHeaderSize; 263 264 if ((pPayloadBuffer == NULL) && (packetState != MCTP_PACKET_STATE_SINGLE_PACKET)) 265 { 266 NVSWITCH_PRINT(device, ERROR, "No buffer provided when receiving multi-packet message. Buffer needed to reconstruct message\n"); 267 status = -NVL_ERR_GENERIC; 268 goto done; 269 } 270 271 if (pPayloadBuffer != NULL) 272 { 273 if (payloadBufferSize < (totalPayloadSize + curPayloadSize)) 274 { 275 NVSWITCH_PRINT(device, ERROR, "Buffer provided for message payload too small. Payload size: 0x%x Buffer size: 0x%x\n", 276 totalPayloadSize + curPayloadSize, payloadBufferSize); 277 status = -NVL_ERR_GENERIC; 278 goto done; 279 } 280 nvswitch_os_memcpy(pPayloadBuffer + totalPayloadSize, 281 pPacketBuffer + curHeaderSize, curPayloadSize); 282 } 283 totalPayloadSize += curPayloadSize; 284 285 // Set TAIL = HEAD to indicate CPU received message 286 nvswitch_fsp_update_msgq_head_tail(device, msgqHead, msgqHead); 287 } 288 289 pMessagePayload = (pPayloadBuffer == NULL) ? (pPacketBuffer + sizeof(MCTP_HEADER)) : pPayloadBuffer; 290 291 status = nvswitch_fsp_process_nvdm_msg(device, pMessagePayload, totalPayloadSize); 292 293 done: 294 nvswitch_os_free(pPacketBuffer); 295 return status; 296 } 297 298 /*! 299 * @brief Send one MCTP packet to FSP via EMEM 300 * 301 * @param[in] device nvswitch_device pointer 302 * @param[in] pPacket MCTP packet 303 * @param[in] packetSize MCTP packet size in bytes 304 * 305 * @return NVL_SUCCESS, or NV_ERR_INSUFFICIENT_RESOURCES 306 */ 307 NvlStatus 308 nvswitch_fsp_send_packet 309 ( 310 nvswitch_device *device, 311 NvU8 *pPacket, 312 NvU32 packetSize 313 ) 314 { 315 NvU32 paddedSize; 316 NvU8 *pBuffer = NULL; 317 NV_STATUS status = NVL_SUCCESS; 318 319 // Check that queue is ready to receive data 320 status = _nvswitch_fsp_poll_for_queue_empty(device); 321 if (status != NVL_SUCCESS) 322 { 323 return -NVL_ERR_GENERIC; 324 } 325 326 // Pad to align size to 4-bytes boundary since EMEMC increments by DWORDS 327 paddedSize = NV_ALIGN_UP(packetSize, sizeof(NvU32)); 328 pBuffer = nvswitch_os_malloc(paddedSize); 329 if (pBuffer == NULL) 330 { 331 NVSWITCH_PRINT(device, ERROR, 332 "Failed to allocate memory!!\n"); 333 return -NVL_NO_MEM; 334 } 335 nvswitch_os_memset(pBuffer, 0, paddedSize); 336 nvswitch_os_memcpy(pBuffer, pPacket, paddedSize); 337 338 nvswitch_fsp_write_to_emem(device, pBuffer, paddedSize); 339 340 // Update HEAD and TAIL with new EMEM offset; RM always starts at offset 0. 341 nvswitch_fsp_update_cmdq_head_tail(device, 0, paddedSize - sizeof(NvU32)); 342 343 nvswitch_os_free(pBuffer); 344 return status; 345 } 346 347 /*! 348 * @brief Send a MCTP message to FSP via EMEM, and read response 349 * 350 * 351 * Response payload buffer is optional if response fits in a single packet. 352 * 353 * @param[in] device nvswitch_device pointer 354 * @param[in] pPayload Pointer to message payload 355 * @param[in] size Message payload size 356 * @param[in] nvdmType NVDM type of message being sent 357 * @param[in] pResponsePayload Buffer in which to return response payload 358 * @param[in] responseBufferSize Response payload buffer size 359 * 360 * @return NVL_SUCCESS, or NV_ERR_* 361 */ 362 NvlStatus 363 nvswitch_fsp_send_and_read_message 364 ( 365 nvswitch_device *device, 366 NvU8 *pPayload, 367 NvU32 size, 368 NvU32 nvdmType, 369 NvU8 *pResponsePayload, 370 NvU32 responseBufferSize 371 ) 372 { 373 NvU32 dataSent, dataRemaining; 374 NvU32 packetPayloadCapacity; 375 NvU32 curPayloadSize; 376 NvU32 headerSize; 377 NvU32 fspEmemChannelSize; 378 NvBool bSinglePacket; 379 NV_STATUS status; 380 NvU8 *pBuffer = NULL; 381 NvU8 seq = 0; 382 NvU8 seid = 0; 383 384 // Allocate buffer of same size as channel 385 fspEmemChannelSize = nvswitch_fsp_get_channel_size(device); 386 pBuffer = nvswitch_os_malloc(fspEmemChannelSize); 387 if (pBuffer == NULL) 388 { 389 NVSWITCH_PRINT(device, ERROR, 390 "%s: Failed to allocate memory!!\n", 391 __FUNCTION__); 392 return -NVL_NO_MEM; 393 394 } 395 396 nvswitch_os_memset(pBuffer, 0, fspEmemChannelSize); 397 398 // 399 // Check if message will fit in single packet 400 // We lose 2 DWORDS to MCTP and NVDM headers 401 // 402 headerSize = 2 * sizeof(NvU32); 403 packetPayloadCapacity = fspEmemChannelSize - headerSize; 404 bSinglePacket = (size <= packetPayloadCapacity); 405 406 // First packet 407 seid = nvswitch_fsp_nvdm_to_seid(device, nvdmType); 408 ((NvU32 *)pBuffer)[0] = nvswitch_fsp_create_mctp_header(device, 1, (NvU8)bSinglePacket, seid, seq); // SOM=1,EOM=?,SEID,SEQ=0 409 ((NvU32 *)pBuffer)[1] = nvswitch_fsp_create_nvdm_header(device, nvdmType); 410 411 curPayloadSize = NV_MIN(size, packetPayloadCapacity); 412 nvswitch_os_memcpy(pBuffer + headerSize, pPayload, curPayloadSize); 413 414 status = nvswitch_fsp_send_packet(device, pBuffer, curPayloadSize + headerSize); 415 if (status != NVL_SUCCESS) 416 { 417 goto failed; 418 } 419 420 if (!bSinglePacket) 421 { 422 // Multi packet case 423 dataSent = curPayloadSize; 424 dataRemaining = size - dataSent; 425 headerSize = sizeof(NvU32); // No longer need NVDM header 426 packetPayloadCapacity = fspEmemChannelSize - headerSize; 427 428 while (dataRemaining > 0) 429 { 430 NvBool bLastPacket = (dataRemaining <= packetPayloadCapacity); 431 curPayloadSize = (bLastPacket) ? dataRemaining : packetPayloadCapacity; 432 433 nvswitch_os_memset(pBuffer, 0, fspEmemChannelSize); 434 ((NvU32 *)pBuffer)[0] = nvswitch_fsp_create_mctp_header(device, 0, (NvU8)bLastPacket, seid, (++seq) % 4); 435 436 nvswitch_os_memcpy(pBuffer + headerSize, pPayload + dataSent, curPayloadSize); 437 438 status = nvswitch_fsp_send_packet(device, pBuffer, curPayloadSize + headerSize); 439 if (status != NVL_SUCCESS) 440 { 441 goto failed; 442 } 443 444 dataSent += curPayloadSize; 445 dataRemaining -= curPayloadSize; 446 } 447 } 448 449 status = _nvswitch_fsp_poll_for_response(device); 450 if (status != NVL_SUCCESS) 451 { 452 goto failed; 453 } 454 status = nvswitch_fsp_read_message(device, pResponsePayload, responseBufferSize); 455 456 failed: 457 nvswitch_os_free(pBuffer); 458 459 return status; 460 } 461