286 lines · c
1// SPDX-License-Identifier: GPL-2.0-only OR MIT2/* Copyright (c) 2023 Imagination Technologies Ltd. */3 4#include "pvr_device.h"5#include "pvr_rogue_fwif_stream.h"6#include "pvr_stream.h"7 8#include <linux/align.h>9#include <linux/slab.h>10#include <linux/types.h>11#include <uapi/drm/pvr_drm.h>12 13static __always_inline bool14stream_def_is_supported(struct pvr_device *pvr_dev, const struct pvr_stream_def *stream_def)15{16 if (stream_def->feature == PVR_FEATURE_NONE)17 return true;18 19 if (!(stream_def->feature & PVR_FEATURE_NOT) &&20 pvr_device_has_feature(pvr_dev, stream_def->feature)) {21 return true;22 }23 24 if ((stream_def->feature & PVR_FEATURE_NOT) &&25 !pvr_device_has_feature(pvr_dev, stream_def->feature & ~PVR_FEATURE_NOT)) {26 return true;27 }28 29 return false;30}31 32static int33pvr_stream_get_data(u8 *stream, u32 *stream_offset, u32 stream_size, u32 data_size, u32 align_size,34 void *dest)35{36 *stream_offset = ALIGN(*stream_offset, align_size);37 38 if ((*stream_offset + data_size) > stream_size)39 return -EINVAL;40 41 memcpy(dest, stream + *stream_offset, data_size);42 43 (*stream_offset) += data_size;44 45 return 0;46}47 48/**49 * pvr_stream_process_1() - Process a single stream and fill destination structure50 * @pvr_dev: Device pointer.51 * @stream_def: Stream definition.52 * @nr_entries: Number of entries in &stream_def.53 * @stream: Pointer to stream.54 * @stream_offset: Starting offset within stream.55 * @stream_size: Size of input stream, in bytes.56 * @dest: Pointer to destination structure.57 * @dest_size: Size of destination structure.58 * @stream_offset_out: Pointer to variable to write updated stream offset to. May be NULL.59 *60 * Returns:61 * * 0 on success, or62 * * -%EINVAL on malformed stream.63 */64static int65pvr_stream_process_1(struct pvr_device *pvr_dev, const struct pvr_stream_def *stream_def,66 u32 nr_entries, u8 *stream, u32 stream_offset, u32 stream_size,67 u8 *dest, u32 dest_size, u32 *stream_offset_out)68{69 int err = 0;70 u32 i;71 72 for (i = 0; i < nr_entries; i++) {73 if (stream_def[i].offset >= dest_size) {74 err = -EINVAL;75 break;76 }77 78 if (!stream_def_is_supported(pvr_dev, &stream_def[i]))79 continue;80 81 switch (stream_def[i].size) {82 case PVR_STREAM_SIZE_8:83 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u8),84 sizeof(u8), dest + stream_def[i].offset);85 if (err)86 return err;87 break;88 89 case PVR_STREAM_SIZE_16:90 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u16),91 sizeof(u16), dest + stream_def[i].offset);92 if (err)93 return err;94 break;95 96 case PVR_STREAM_SIZE_32:97 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u32),98 sizeof(u32), dest + stream_def[i].offset);99 if (err)100 return err;101 break;102 103 case PVR_STREAM_SIZE_64:104 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u64),105 sizeof(u64), dest + stream_def[i].offset);106 if (err)107 return err;108 break;109 110 case PVR_STREAM_SIZE_ARRAY:111 err = pvr_stream_get_data(stream, &stream_offset, stream_size,112 stream_def[i].array_size, sizeof(u64),113 dest + stream_def[i].offset);114 if (err)115 return err;116 break;117 }118 }119 120 if (stream_offset_out)121 *stream_offset_out = stream_offset;122 123 return err;124}125 126static int127pvr_stream_process_ext_stream(struct pvr_device *pvr_dev,128 const struct pvr_stream_cmd_defs *cmd_defs, void *ext_stream,129 u32 stream_offset, u32 ext_stream_size, void *dest)130{131 u32 musthave_masks[PVR_STREAM_EXTHDR_TYPE_MAX];132 u32 ext_header;133 int err = 0;134 u32 i;135 136 /* Copy "must have" mask from device. We clear this as we process the stream. */137 memcpy(musthave_masks, pvr_dev->stream_musthave_quirks[cmd_defs->type],138 sizeof(musthave_masks));139 140 do {141 const struct pvr_stream_ext_header *header;142 u32 type;143 u32 data;144 145 err = pvr_stream_get_data(ext_stream, &stream_offset, ext_stream_size, sizeof(u32),146 sizeof(ext_header), &ext_header);147 if (err)148 return err;149 150 type = (ext_header & PVR_STREAM_EXTHDR_TYPE_MASK) >> PVR_STREAM_EXTHDR_TYPE_SHIFT;151 data = ext_header & PVR_STREAM_EXTHDR_DATA_MASK;152 153 if (type >= cmd_defs->ext_nr_headers)154 return -EINVAL;155 156 header = &cmd_defs->ext_headers[type];157 if (data & ~header->valid_mask)158 return -EINVAL;159 160 musthave_masks[type] &= ~data;161 162 for (i = 0; i < header->ext_streams_num; i++) {163 const struct pvr_stream_ext_def *ext_def = &header->ext_streams[i];164 165 if (!(ext_header & ext_def->header_mask))166 continue;167 168 if (!pvr_device_has_uapi_quirk(pvr_dev, ext_def->quirk))169 return -EINVAL;170 171 err = pvr_stream_process_1(pvr_dev, ext_def->stream, ext_def->stream_len,172 ext_stream, stream_offset,173 ext_stream_size, dest,174 cmd_defs->dest_size, &stream_offset);175 if (err)176 return err;177 }178 } while (ext_header & PVR_STREAM_EXTHDR_CONTINUATION);179 180 /*181 * Verify that "must have" mask is now zero. If it isn't then one of the "must have" quirks182 * for this command was not present.183 */184 for (i = 0; i < cmd_defs->ext_nr_headers; i++) {185 if (musthave_masks[i])186 return -EINVAL;187 }188 189 return 0;190}191 192/**193 * pvr_stream_process() - Build FW structure from stream194 * @pvr_dev: Device pointer.195 * @cmd_defs: Stream definition.196 * @stream: Pointer to command stream.197 * @stream_size: Size of command stream, in bytes.198 * @dest_out: Pointer to destination buffer.199 *200 * Caller is responsible for freeing the output structure.201 *202 * Returns:203 * * 0 on success,204 * * -%ENOMEM on out of memory, or205 * * -%EINVAL on malformed stream.206 */207int208pvr_stream_process(struct pvr_device *pvr_dev, const struct pvr_stream_cmd_defs *cmd_defs,209 void *stream, u32 stream_size, void *dest_out)210{211 u32 stream_offset = 0;212 u32 main_stream_len;213 u32 padding;214 int err;215 216 if (!stream || !stream_size)217 return -EINVAL;218 219 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u32),220 sizeof(u32), &main_stream_len);221 if (err)222 return err;223 224 /*225 * u32 after stream length is padding to ensure u64 alignment, but may be used for expansion226 * in the future. Verify it's zero.227 */228 err = pvr_stream_get_data(stream, &stream_offset, stream_size, sizeof(u32),229 sizeof(u32), &padding);230 if (err)231 return err;232 233 if (main_stream_len < stream_offset || main_stream_len > stream_size || padding)234 return -EINVAL;235 236 err = pvr_stream_process_1(pvr_dev, cmd_defs->main_stream, cmd_defs->main_stream_len,237 stream, stream_offset, main_stream_len, dest_out,238 cmd_defs->dest_size, &stream_offset);239 if (err)240 return err;241 242 if (stream_offset < stream_size) {243 err = pvr_stream_process_ext_stream(pvr_dev, cmd_defs, stream, stream_offset,244 stream_size, dest_out);245 if (err)246 return err;247 } else {248 u32 i;249 250 /*251 * If we don't have an extension stream then there must not be any "must have"252 * quirks for this command.253 */254 for (i = 0; i < cmd_defs->ext_nr_headers; i++) {255 if (pvr_dev->stream_musthave_quirks[cmd_defs->type][i])256 return -EINVAL;257 }258 }259 260 return 0;261}262 263/**264 * pvr_stream_create_musthave_masks() - Create "must have" masks for streams based on current device265 * quirks266 * @pvr_dev: Device pointer.267 */268void269pvr_stream_create_musthave_masks(struct pvr_device *pvr_dev)270{271 memset(pvr_dev->stream_musthave_quirks, 0, sizeof(pvr_dev->stream_musthave_quirks));272 273 if (pvr_device_has_uapi_quirk(pvr_dev, 47217))274 pvr_dev->stream_musthave_quirks[PVR_STREAM_TYPE_FRAG][0] |=275 PVR_STREAM_EXTHDR_FRAG0_BRN47217;276 277 if (pvr_device_has_uapi_quirk(pvr_dev, 49927)) {278 pvr_dev->stream_musthave_quirks[PVR_STREAM_TYPE_GEOM][0] |=279 PVR_STREAM_EXTHDR_GEOM0_BRN49927;280 pvr_dev->stream_musthave_quirks[PVR_STREAM_TYPE_FRAG][0] |=281 PVR_STREAM_EXTHDR_FRAG0_BRN49927;282 pvr_dev->stream_musthave_quirks[PVR_STREAM_TYPE_COMPUTE][0] |=283 PVR_STREAM_EXTHDR_COMPUTE0_BRN49927;284 }285}286