Line data Source code
1 : /*
2 : * If not stated otherwise in this file or this component's LICENSE file the
3 : * following copyright and licenses apply:
4 : *
5 : * Copyright 2022 Sky UK
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : #include <algorithm>
21 : #include <cstring>
22 : #include <numeric>
23 : #include <stdexcept>
24 :
25 : #include <fcntl.h>
26 : #include <sys/mman.h>
27 : #include <syscall.h>
28 : #include <unistd.h>
29 :
30 : #include "RialtoServerLogging.h"
31 : #include "SharedMemoryBuffer.h"
32 : #include "ShmUtils.h"
33 : #include "TypeConverters.h"
34 :
35 : #if !defined(SYS_memfd_create)
36 : #if defined(__NR_memfd_create)
37 : #define SYS_memfd_create __NR_memfd_create
38 : #elif defined(__arm__)
39 : #define SYS_memfd_create 385
40 : #endif
41 : #endif
42 :
43 : #if !defined(MFD_CLOEXEC)
44 : #define MFD_CLOEXEC 0x0001U
45 : #endif
46 :
47 : #if !defined(MFD_ALLOW_SEALING)
48 : #define MFD_ALLOW_SEALING 0x0002U
49 : #endif
50 :
51 : #if !defined(F_ADD_SEALS)
52 : #if !defined(F_LINUX_SPECIFIC_BASE)
53 : #define F_LINUX_SPECIFIC_BASE 1024
54 : #endif
55 : #define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
56 : #define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
57 :
58 : #define F_SEAL_SEAL 0x0001
59 : #define F_SEAL_SHRINK 0x0002
60 : #define F_SEAL_GROW 0x0004
61 : #define F_SEAL_WRITE 0x0008
62 : #endif
63 :
64 : namespace
65 : {
66 : const char *kMemoryBufferName{"rialto_avbuf"};
67 : constexpr int kNoIdAssigned{-1};
68 : constexpr uint32_t kVideoRegionSize = 7 * 1024 * 1024; // 7MB
69 : constexpr uint32_t kAudioRegionSize = 1 * 1024 * 1024; // 1MB
70 : constexpr uint32_t kSubtitleRegionSize = 256 * 1024; // 256kB
71 : constexpr uint32_t kWebAudioRegionSize = 10 * 1024; // 10KB
72 :
73 : std::vector<firebolt::rialto::server::SharedMemoryBuffer::Partition>
74 106 : calculatePartitionSize(firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType playbackType, int num)
75 : {
76 106 : if (firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::GENERIC == playbackType)
77 : {
78 : // As (for now) resolution of playback (for example HD or UHD) is not known, partitions have the same size.
79 53 : firebolt::rialto::server::SharedMemoryBuffer::Partition singlePlaybackDataBuffer{kNoIdAssigned, kAudioRegionSize,
80 : kVideoRegionSize,
81 : kSubtitleRegionSize};
82 106 : return std::vector<firebolt::rialto::server::SharedMemoryBuffer::Partition>(num, singlePlaybackDataBuffer);
83 : }
84 53 : else if (firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::WEB_AUDIO == playbackType)
85 : {
86 53 : firebolt::rialto::server::SharedMemoryBuffer::Partition webAudioDataBuffer{kNoIdAssigned, kWebAudioRegionSize, 0};
87 106 : return std::vector<firebolt::rialto::server::SharedMemoryBuffer::Partition>(num, webAudioDataBuffer);
88 : }
89 : else
90 : {
91 0 : return std::vector<firebolt::rialto::server::SharedMemoryBuffer::Partition>();
92 : }
93 : }
94 :
95 18 : const char *toString(const firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType &type)
96 : {
97 18 : switch (type)
98 : {
99 13 : case firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::GENERIC:
100 : {
101 13 : return "GENERIC";
102 : }
103 5 : case firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::WEB_AUDIO:
104 : {
105 5 : return "WEB_AUDIO";
106 : }
107 : }
108 0 : return "UNKNOWN";
109 : }
110 :
111 5 : void releasePages(std::uint8_t *data, std::uint32_t length)
112 : {
113 5 : const auto kPageSize{sysconf(_SC_PAGESIZE)};
114 5 : if (kPageSize <= 0)
115 : {
116 0 : RIALTO_SERVER_LOG_SYS_WARN(errno, "Failed to get the page size, shm pages not released");
117 0 : return;
118 : }
119 5 : const std::uintptr_t kPage{static_cast<std::uintptr_t>(kPageSize)};
120 5 : const std::uintptr_t kStart{reinterpret_cast<std::uintptr_t>(data)};
121 5 : const std::uintptr_t kBegin{(kStart + kPage - 1) / kPage * kPage};
122 5 : const std::uintptr_t kEnd{(kStart + length) / kPage * kPage};
123 5 : if (kEnd <= kBegin)
124 : {
125 0 : return;
126 : }
127 : // coverity[INTEGER_OVERFLOW:FALSE]
128 5 : if (madvise(reinterpret_cast<void *>(kBegin), kEnd - kBegin, MADV_DONTNEED) != 0)
129 : {
130 1 : RIALTO_SERVER_LOG_SYS_WARN(errno, "Failed to release shm pages");
131 : }
132 : }
133 :
134 : // Client frame writers re-zero the payload (V2) or only use length-addressed payload (V1), so only the header needs a reset.
135 8 : size_t getClearLen(firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType playbackType, uint32_t regionLen)
136 : {
137 8 : if (firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::GENERIC == playbackType)
138 : {
139 6 : return std::min(regionLen, firebolt::rialto::server::getMaxMetadataBytes());
140 : }
141 2 : return regionLen;
142 : }
143 : } // namespace
144 :
145 : namespace firebolt::rialto::server
146 : {
147 2 : std::unique_ptr<ISharedMemoryBufferFactory> ISharedMemoryBufferFactory::createFactory()
148 : {
149 2 : return std::make_unique<SharedMemoryBufferFactory>();
150 : }
151 :
152 : std::shared_ptr<ISharedMemoryBuffer>
153 53 : SharedMemoryBufferFactory::createSharedMemoryBuffer(unsigned numOfPlaybacks, unsigned numOfWebAudioPlayers) const
154 : {
155 53 : return std::make_shared<SharedMemoryBuffer>(numOfPlaybacks, numOfWebAudioPlayers);
156 : }
157 :
158 53 : SharedMemoryBuffer::SharedMemoryBuffer(unsigned numOfPlaybacks, unsigned numOfWebAudioPlayers)
159 53 : : m_genericPartitions{calculatePartitionSize(MediaPlaybackType::GENERIC, numOfPlaybacks)},
160 53 : m_webAudioPartitions{calculatePartitionSize(MediaPlaybackType::WEB_AUDIO, numOfWebAudioPlayers)},
161 53 : m_dataBufferLen{0}, m_dataBufferFd{-1}, m_dataBuffer{nullptr}
162 : {
163 53 : int fd = syscall(SYS_memfd_create, kMemoryBufferName, MFD_CLOEXEC | MFD_ALLOW_SEALING);
164 53 : if (fd < 0)
165 : {
166 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to create memory buffer");
167 : }
168 : else
169 : {
170 53 : const size_t kBufferSize{calculateBufferSize()};
171 53 : if (ftruncate(fd, static_cast<off_t>(kBufferSize)) == -1)
172 : {
173 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to resize memfd");
174 : }
175 53 : else if (fcntl(fd, F_ADD_SEALS, (F_SEAL_SEAL | F_SEAL_GROW | F_SEAL_SHRINK)) == -1)
176 : {
177 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to seal memfd");
178 : }
179 : else
180 : {
181 53 : void *addr = mmap(nullptr, kBufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
182 53 : if (addr != MAP_FAILED)
183 : {
184 53 : m_dataBufferLen = kBufferSize;
185 53 : m_dataBufferFd = fd;
186 53 : m_dataBuffer = reinterpret_cast<uint8_t *>(addr);
187 53 : RIALTO_SERVER_LOG_INFO("Shared Memory Buffer size: %d, ptr: %p", m_dataBufferLen, m_dataBuffer);
188 : }
189 : }
190 :
191 53 : if (m_dataBufferFd == -1)
192 : {
193 0 : if (close(fd) != 0)
194 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to close fd");
195 : }
196 : }
197 :
198 53 : if (m_dataBufferFd == -1)
199 : {
200 0 : RIALTO_SERVER_LOG_ERROR("Shared Memory Buffer initialization failed");
201 0 : throw std::runtime_error("Shared Memory Buffer initialization failed");
202 : }
203 53 : }
204 :
205 53 : SharedMemoryBuffer::~SharedMemoryBuffer()
206 : {
207 53 : RIALTO_SERVER_LOG_INFO("Destroying Shared Memory Buffer");
208 53 : if (m_dataBufferFd != -1)
209 : {
210 53 : if (munmap(m_dataBuffer, m_dataBufferLen) != 0)
211 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to unmap buffer");
212 53 : if (close(m_dataBufferFd) != 0)
213 0 : RIALTO_SERVER_LOG_SYS_ERROR(errno, "failed to close data buffer fd");
214 :
215 53 : m_dataBufferLen = 0;
216 53 : m_dataBufferFd = -1;
217 53 : m_dataBuffer = nullptr;
218 : }
219 : }
220 :
221 49 : bool SharedMemoryBuffer::mapPartition(MediaPlaybackType playbackType, int id)
222 : {
223 49 : std::vector<Partition> *partitions = getPlaybackTypePartition(playbackType);
224 49 : if (!partitions)
225 : {
226 0 : RIALTO_SERVER_LOG_ERROR("Cannot map the partition for playback type %s with id: %d", toString(playbackType), id);
227 0 : return false;
228 : }
229 :
230 108 : auto partition = std::find_if(partitions->begin(), partitions->end(), [id](const auto &p) { return p.id == id; });
231 49 : if (partition != partitions->end())
232 : {
233 2 : RIALTO_SERVER_LOG_DEBUG("Skip to map shm partition for id: %d. - partition already assigned", id);
234 2 : return true;
235 : }
236 : auto freePartition =
237 99 : std::find_if(partitions->begin(), partitions->end(), [](const auto &p) { return p.id == kNoIdAssigned; });
238 47 : if (freePartition == partitions->end())
239 : {
240 2 : RIALTO_SERVER_LOG_ERROR("Failed to map Shm partition for id: %d. No free partition available.", id);
241 2 : return false;
242 : }
243 45 : freePartition->id = id;
244 45 : return true;
245 : }
246 :
247 7 : bool SharedMemoryBuffer::unmapPartition(MediaPlaybackType playbackType, int id)
248 : {
249 7 : std::vector<Partition> *partitions = getPlaybackTypePartition(playbackType);
250 7 : if (!partitions)
251 : {
252 0 : RIALTO_SERVER_LOG_ERROR("Cannot unmap the partition for playback type %s with id: %d", toString(playbackType),
253 : id);
254 0 : return false;
255 : }
256 :
257 14 : auto partition = std::find_if(partitions->begin(), partitions->end(), [id](const auto &p) { return p.id == id; });
258 7 : if (partition == partitions->end())
259 : {
260 2 : RIALTO_SERVER_LOG_WARN("Failed to unmap Shm partition for id: %d. - partition could not be found", id);
261 2 : return false;
262 : }
263 5 : std::uint8_t *partitionDataPtr = nullptr;
264 5 : if (getDataPtrForPartition(playbackType, id, &partitionDataPtr))
265 : {
266 5 : releasePages(partitionDataPtr,
267 5 : partition->dataBufferVideoLen + partition->dataBufferAudioLen + partition->dataBufferSubtitleLen);
268 : }
269 5 : partition->id = kNoIdAssigned;
270 5 : return true;
271 : }
272 :
273 12 : bool SharedMemoryBuffer::clearData(MediaPlaybackType playbackType, int id, const MediaSourceType &mediaSourceType) const
274 : {
275 12 : const std::vector<Partition> *kPartitions = getPlaybackTypePartition(playbackType);
276 12 : if (!kPartitions)
277 : {
278 0 : RIALTO_SERVER_LOG_ERROR("Cannot clear the %s data for playback type %s with id: %d",
279 : common::convertMediaSourceType(mediaSourceType), toString(playbackType), id);
280 0 : return false;
281 : }
282 :
283 24 : auto partition = std::find_if(kPartitions->begin(), kPartitions->end(), [id](const auto &p) { return p.id == id; });
284 12 : if (partition == kPartitions->end())
285 : {
286 4 : RIALTO_SERVER_LOG_WARN("Failed to clear %s data for playback type %s with id: %d. - partition could not be "
287 : "found",
288 : common::convertMediaSourceType(mediaSourceType), toString(playbackType), id);
289 4 : return false;
290 : }
291 8 : std::uint8_t *partitionDataPtr = nullptr;
292 8 : if (!getDataPtrForPartition(playbackType, id, &partitionDataPtr))
293 : {
294 0 : RIALTO_SERVER_LOG_ERROR("Failed to clear %s data for playback type %s with id: %d. - could not get partition "
295 : "data "
296 : "ptr",
297 : common::convertMediaSourceType(mediaSourceType), toString(playbackType), id);
298 0 : return false;
299 : }
300 :
301 8 : if (MediaSourceType::VIDEO == mediaSourceType)
302 : {
303 2 : std::uint8_t *videoData = partitionDataPtr;
304 2 : memset(videoData, 0x00, getClearLen(playbackType, partition->dataBufferVideoLen));
305 2 : return true;
306 : }
307 6 : if (MediaSourceType::AUDIO == mediaSourceType)
308 : {
309 4 : std::uint8_t *audioData = partitionDataPtr + partition->dataBufferVideoLen;
310 4 : memset(audioData, 0x00, getClearLen(playbackType, partition->dataBufferAudioLen));
311 4 : return true;
312 : }
313 2 : if (MediaSourceType::SUBTITLE == mediaSourceType)
314 : {
315 2 : std::uint8_t *subtitleoData = partitionDataPtr + partition->dataBufferVideoLen + partition->dataBufferAudioLen;
316 2 : memset(subtitleoData, 0x00, getClearLen(playbackType, partition->dataBufferSubtitleLen));
317 2 : return true;
318 : }
319 :
320 0 : return false;
321 : }
322 :
323 13 : std::uint32_t SharedMemoryBuffer::getDataOffset(MediaPlaybackType playbackType, int id,
324 : const MediaSourceType &mediaSourceType) const
325 : {
326 13 : std::uint8_t *buffer = getDataPtr(playbackType, id, mediaSourceType);
327 13 : if (!buffer)
328 : {
329 8 : throw std::runtime_error("Buffer not found for playback type " + std::string(toString(playbackType)) +
330 12 : " with id: " + std::to_string(id));
331 : }
332 9 : return buffer - m_dataBuffer;
333 : }
334 :
335 14 : std::uint32_t SharedMemoryBuffer::getMaxDataLen(MediaPlaybackType playbackType, int id,
336 : const MediaSourceType &mediaSourceType) const
337 : {
338 14 : const std::vector<Partition> *kPartitions = getPlaybackTypePartition(playbackType);
339 14 : if (!kPartitions)
340 : {
341 0 : RIALTO_SERVER_LOG_ERROR("Cannot get the max data length for playback type %s with id: %d type: %s",
342 : toString(playbackType), id, common::convertMediaSourceType(mediaSourceType));
343 0 : return false;
344 : }
345 :
346 28 : auto partition = std::find_if(kPartitions->begin(), kPartitions->end(), [id](const auto &p) { return p.id == id; });
347 14 : if (partition == kPartitions->end())
348 : {
349 3 : RIALTO_SERVER_LOG_WARN("Failed to get buffer length for playback type %s with id: %d. type: %s- partition "
350 : "could not be "
351 : "found",
352 : toString(playbackType), id, common::convertMediaSourceType(mediaSourceType));
353 3 : return 0;
354 : }
355 :
356 11 : if (MediaSourceType::VIDEO == mediaSourceType)
357 : {
358 4 : return partition->dataBufferVideoLen;
359 : }
360 7 : if (MediaSourceType::AUDIO == mediaSourceType)
361 : {
362 5 : return partition->dataBufferAudioLen;
363 : }
364 2 : if (MediaSourceType::SUBTITLE == mediaSourceType)
365 : {
366 2 : return partition->dataBufferSubtitleLen;
367 : }
368 :
369 0 : return 0;
370 : }
371 :
372 33 : std::uint8_t *SharedMemoryBuffer::getDataPtr(MediaPlaybackType playbackType, int id,
373 : const MediaSourceType &mediaSourceType) const
374 : {
375 33 : const std::vector<Partition> *kPartitions = getPlaybackTypePartition(playbackType);
376 33 : if (!kPartitions)
377 : {
378 0 : RIALTO_SERVER_LOG_ERROR("Cannot get the buffer offset for playback type %s with id: %d, type: %s",
379 : toString(playbackType), id, common::convertMediaSourceType(mediaSourceType));
380 0 : return nullptr;
381 : }
382 :
383 73 : auto partition = std::find_if(kPartitions->begin(), kPartitions->end(), [id](const auto &p) { return p.id == id; });
384 33 : if (partition == kPartitions->end())
385 : {
386 7 : RIALTO_SERVER_LOG_WARN("Failed to get buffer offset for playback type %s with id: %d, type: %s. - partition "
387 : "could not be "
388 : "found",
389 : toString(playbackType), id, common::convertMediaSourceType(mediaSourceType));
390 7 : return nullptr;
391 : }
392 26 : std::uint8_t *partitionDataPtr = nullptr;
393 26 : if (!getDataPtrForPartition(playbackType, id, &partitionDataPtr))
394 : {
395 0 : RIALTO_SERVER_LOG_ERROR("Failed to get buffer offset for playback type %s with id: %d. - could not get "
396 : "partition data ptr",
397 : toString(playbackType), id);
398 0 : return nullptr;
399 : }
400 :
401 26 : if ((MediaSourceType::VIDEO == mediaSourceType) && (0 != partition->dataBufferVideoLen))
402 : {
403 9 : return partitionDataPtr;
404 : }
405 17 : if ((MediaSourceType::AUDIO == mediaSourceType) && (0 != partition->dataBufferAudioLen))
406 : {
407 12 : return partitionDataPtr + partition->dataBufferVideoLen;
408 : }
409 5 : if ((MediaSourceType::SUBTITLE == mediaSourceType) && (0 != partition->dataBufferSubtitleLen))
410 : {
411 3 : return partitionDataPtr + partition->dataBufferVideoLen + partition->dataBufferAudioLen;
412 : }
413 :
414 2 : return nullptr;
415 : }
416 :
417 1 : int SharedMemoryBuffer::getFd() const
418 : {
419 1 : return m_dataBufferFd;
420 : }
421 :
422 1 : std::uint32_t SharedMemoryBuffer::getSize() const
423 : {
424 1 : return m_dataBufferLen;
425 : }
426 :
427 3 : std::uint8_t *SharedMemoryBuffer::getBuffer() const
428 : {
429 3 : return m_dataBuffer;
430 : }
431 :
432 53 : size_t SharedMemoryBuffer::calculateBufferSize() const
433 : {
434 : size_t genericSum =
435 53 : std::accumulate(m_genericPartitions.begin(), m_genericPartitions.end(), 0, [](size_t sum, const Partition &p)
436 57 : { return sum + p.dataBufferAudioLen + p.dataBufferVideoLen + p.dataBufferSubtitleLen; });
437 53 : size_t webAudioSum = std::accumulate(m_webAudioPartitions.begin(), m_webAudioPartitions.end(), 0,
438 57 : [](size_t sum, const Partition &p)
439 57 : { return sum + p.dataBufferAudioLen + p.dataBufferVideoLen; });
440 53 : return genericSum + webAudioSum;
441 : }
442 :
443 39 : bool SharedMemoryBuffer::getDataPtrForPartition(MediaPlaybackType playbackType, int id, std::uint8_t **ptr) const
444 : {
445 39 : std::uint8_t *result = m_dataBuffer;
446 :
447 57 : for (const auto &partition : m_genericPartitions)
448 : {
449 47 : if ((MediaPlaybackType::GENERIC == playbackType) && (partition.id == id))
450 : {
451 29 : *ptr = result;
452 29 : return true;
453 : }
454 18 : result += (partition.dataBufferVideoLen + partition.dataBufferAudioLen + partition.dataBufferSubtitleLen);
455 : }
456 :
457 12 : for (const auto &partition : m_webAudioPartitions)
458 : {
459 12 : if ((MediaPlaybackType::WEB_AUDIO == playbackType) && (partition.id == id))
460 : {
461 10 : *ptr = result;
462 10 : return true;
463 : }
464 2 : result += (partition.dataBufferVideoLen + partition.dataBufferAudioLen + partition.dataBufferSubtitleLen);
465 : }
466 :
467 0 : RIALTO_SERVER_LOG_ERROR("Could not find the data ptr for playback type %s with id: %d", toString(playbackType), id);
468 0 : return false;
469 : }
470 : const std::vector<SharedMemoryBuffer::Partition> *
471 115 : SharedMemoryBuffer::getPlaybackTypePartition(MediaPlaybackType playbackType) const
472 : {
473 115 : if (firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::GENERIC == playbackType)
474 : {
475 78 : return &m_genericPartitions;
476 : }
477 37 : else if (firebolt::rialto::server::ISharedMemoryBuffer::MediaPlaybackType::WEB_AUDIO == playbackType)
478 : {
479 37 : return &m_webAudioPartitions;
480 : }
481 : else
482 : {
483 0 : RIALTO_SERVER_LOG_ERROR("Invalid playback type");
484 0 : return nullptr;
485 : }
486 : }
487 :
488 56 : std::vector<SharedMemoryBuffer::Partition> *SharedMemoryBuffer::getPlaybackTypePartition(MediaPlaybackType playbackType)
489 : {
490 : return const_cast<std::vector<SharedMemoryBuffer::Partition> *>(
491 56 : const_cast<const SharedMemoryBuffer *>(this)->getPlaybackTypePartition(playbackType));
492 : }
493 : } // namespace firebolt::rialto::server
|