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