1/* 2 ** Copyright 2011, The Android Open Source Project 3 ** 4 ** Licensed under the Apache License, Version 2.0 (the "License"); 5 ** you may not use this file except in compliance with the License. 6 ** You may obtain a copy of the License at 7 ** 8 ** http://www.apache.org/licenses/LICENSE-2.0 9 ** 10 ** Unless required by applicable law or agreed to in writing, software 11 ** distributed under the License is distributed on an "AS IS" BASIS, 12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 ** See the License for the specific language governing permissions and 14 ** limitations under the License. 15 */ 16 17#include "egl_cache.h" 18 19#include "../egl_impl.h" 20 21#include "egl_display.h" 22 23 24#include <private/EGL/cache.h> 25 26#include <inttypes.h> 27#include <sys/mman.h> 28#include <sys/stat.h> 29#include <unistd.h> 30 31#include <thread> 32 33#include <log/log.h> 34 35// Cache size limits. 36static const size_t maxKeySize = 12 * 1024; 37static const size_t maxValueSize = 64 * 1024; 38static const size_t maxTotalSize = 2 * 1024 * 1024; 39 40// Cache file header 41static const char* cacheFileMagic = "EGL$"; 42static const size_t cacheFileHeaderSize = 8; 43 44// The time in seconds to wait before saving newly inserted cache entries. 45static const unsigned int deferredSaveDelay = 4; 46 47// ---------------------------------------------------------------------------- 48namespace android { 49// ---------------------------------------------------------------------------- 50 51#define BC_EXT_STR "EGL_ANDROID_blob_cache" 52 53// called from android_view_ThreadedRenderer.cpp 54void egl_set_cache_filename(const char* filename) { 55 egl_cache_t::get()->setCacheFilename(filename); 56} 57 58// 59// Callback functions passed to EGL. 60// 61static void setBlob(const void* key, EGLsizeiANDROID keySize, 62 const void* value, EGLsizeiANDROID valueSize) { 63 egl_cache_t::get()->setBlob(key, keySize, value, valueSize); 64} 65 66static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, 67 void* value, EGLsizeiANDROID valueSize) { 68 return egl_cache_t::get()->getBlob(key, keySize, value, valueSize); 69} 70 71// 72// egl_cache_t definition 73// 74egl_cache_t::egl_cache_t() : 75 mInitialized(false) { 76} 77 78egl_cache_t::~egl_cache_t() { 79} 80 81egl_cache_t egl_cache_t::sCache; 82 83egl_cache_t* egl_cache_t::get() { 84 return &sCache; 85} 86 87void egl_cache_t::initialize(egl_display_t *display) { 88 std::lock_guard<std::mutex> lock(mMutex); 89 90 egl_connection_t* const cnx = &gEGLImpl; 91 if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { 92 const char* exts = display->disp.queryString.extensions; 93 size_t bcExtLen = strlen(BC_EXT_STR); 94 size_t extsLen = strlen(exts); 95 bool equal = !strcmp(BC_EXT_STR, exts); 96 bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1); 97 bool atEnd = (bcExtLen+1) < extsLen && 98 !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1)); 99 bool inMiddle = strstr(exts, " " BC_EXT_STR " ") != nullptr; 100 if (equal || atStart || atEnd || inMiddle) { 101 PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID; 102 eglSetBlobCacheFuncsANDROID = 103 reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>( 104 cnx->egl.eglGetProcAddress( 105 "eglSetBlobCacheFuncsANDROID")); 106 if (eglSetBlobCacheFuncsANDROID == NULL) { 107 ALOGE("EGL_ANDROID_blob_cache advertised, " 108 "but unable to get eglSetBlobCacheFuncsANDROID"); 109 return; 110 } 111 112 eglSetBlobCacheFuncsANDROID(display->disp.dpy, 113 android::setBlob, android::getBlob); 114 EGLint err = cnx->egl.eglGetError(); 115 if (err != EGL_SUCCESS) { 116 ALOGE("eglSetBlobCacheFuncsANDROID resulted in an error: " 117 "%#x", err); 118 } 119 } 120 } 121 122 mInitialized = true; 123} 124 125void egl_cache_t::terminate() { 126 std::lock_guard<std::mutex> lock(mMutex); 127 saveBlobCacheLocked(); 128 mBlobCache = NULL; 129} 130 131void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, 132 const void* value, EGLsizeiANDROID valueSize) { 133 std::lock_guard<std::mutex> lock(mMutex); 134 135 if (keySize < 0 || valueSize < 0) { 136 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); 137 return; 138 } 139 140 if (mInitialized) { 141 BlobCache* bc = getBlobCacheLocked(); 142 bc->set(key, keySize, value, valueSize); 143 144 if (!mSavePending) { 145 mSavePending = true; 146 std::thread deferredSaveThread([this]() { 147 sleep(deferredSaveDelay); 148 std::lock_guard<std::mutex> lock(mMutex); 149 if (mInitialized) { 150 saveBlobCacheLocked(); 151 } 152 mSavePending = false; 153 }); 154 deferredSaveThread.detach(); 155 } 156 } 157} 158 159EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, 160 void* value, EGLsizeiANDROID valueSize) { 161 std::lock_guard<std::mutex> lock(mMutex); 162 163 if (keySize < 0 || valueSize < 0) { 164 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); 165 return 0; 166 } 167 168 if (mInitialized) { 169 BlobCache* bc = getBlobCacheLocked(); 170 return bc->get(key, keySize, value, valueSize); 171 } 172 return 0; 173} 174 175void egl_cache_t::setCacheFilename(const char* filename) { 176 std::lock_guard<std::mutex> lock(mMutex); 177 mFilename = filename; 178} 179 180BlobCache* egl_cache_t::getBlobCacheLocked() { 181 if (mBlobCache == nullptr) { 182 mBlobCache.reset(new BlobCache(maxKeySize, maxValueSize, maxTotalSize)); 183 loadBlobCacheLocked(); 184 } 185 return mBlobCache.get(); 186} 187 188static uint32_t crc32c(const uint8_t* buf, size_t len) { 189 const uint32_t polyBits = 0x82F63B78; 190 uint32_t r = 0; 191 for (size_t i = 0; i < len; i++) { 192 r ^= buf[i]; 193 for (int j = 0; j < 8; j++) { 194 if (r & 1) { 195 r = (r >> 1) ^ polyBits; 196 } else { 197 r >>= 1; 198 } 199 } 200 } 201 return r; 202} 203 204void egl_cache_t::saveBlobCacheLocked() { 205 if (mFilename.length() > 0 && mBlobCache != NULL) { 206 size_t cacheSize = mBlobCache->getFlattenedSize(); 207 size_t headerSize = cacheFileHeaderSize; 208 const char* fname = mFilename.c_str(); 209 210 // Try to create the file with no permissions so we can write it 211 // without anyone trying to read it. 212 int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); 213 if (fd == -1) { 214 if (errno == EEXIST) { 215 // The file exists, delete it and try again. 216 if (unlink(fname) == -1) { 217 // No point in retrying if the unlink failed. 218 ALOGE("error unlinking cache file %s: %s (%d)", fname, 219 strerror(errno), errno); 220 return; 221 } 222 // Retry now that we've unlinked the file. 223 fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); 224 } 225 if (fd == -1) { 226 ALOGE("error creating cache file %s: %s (%d)", fname, 227 strerror(errno), errno); 228 return; 229 } 230 } 231 232 size_t fileSize = headerSize + cacheSize; 233 234 uint8_t* buf = new uint8_t [fileSize]; 235 if (!buf) { 236 ALOGE("error allocating buffer for cache contents: %s (%d)", 237 strerror(errno), errno); 238 close(fd); 239 unlink(fname); 240 return; 241 } 242 243 int err = mBlobCache->flatten(buf + headerSize, cacheSize); 244 if (err < 0) { 245 ALOGE("error writing cache contents: %s (%d)", strerror(-err), 246 -err); 247 delete [] buf; 248 close(fd); 249 unlink(fname); 250 return; 251 } 252 253 // Write the file magic and CRC 254 memcpy(buf, cacheFileMagic, 4); 255 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); 256 *crc = crc32c(buf + headerSize, cacheSize); 257 258 if (write(fd, buf, fileSize) == -1) { 259 ALOGE("error writing cache file: %s (%d)", strerror(errno), 260 errno); 261 delete [] buf; 262 close(fd); 263 unlink(fname); 264 return; 265 } 266 267 delete [] buf; 268 fchmod(fd, S_IRUSR); 269 close(fd); 270 } 271} 272 273void egl_cache_t::loadBlobCacheLocked() { 274 if (mFilename.length() > 0) { 275 size_t headerSize = cacheFileHeaderSize; 276 277 int fd = open(mFilename.c_str(), O_RDONLY, 0); 278 if (fd == -1) { 279 if (errno != ENOENT) { 280 ALOGE("error opening cache file %s: %s (%d)", mFilename.c_str(), 281 strerror(errno), errno); 282 } 283 return; 284 } 285 286 struct stat statBuf; 287 if (fstat(fd, &statBuf) == -1) { 288 ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno); 289 close(fd); 290 return; 291 } 292 293 // Sanity check the size before trying to mmap it. 294 size_t fileSize = statBuf.st_size; 295 if (fileSize > maxTotalSize * 2) { 296 ALOGE("cache file is too large: %#" PRIx64, 297 static_cast<off64_t>(statBuf.st_size)); 298 close(fd); 299 return; 300 } 301 302 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize, 303 PROT_READ, MAP_PRIVATE, fd, 0)); 304 if (buf == MAP_FAILED) { 305 ALOGE("error mmaping cache file: %s (%d)", strerror(errno), 306 errno); 307 close(fd); 308 return; 309 } 310 311 // Check the file magic and CRC 312 size_t cacheSize = fileSize - headerSize; 313 if (memcmp(buf, cacheFileMagic, 4) != 0) { 314 ALOGE("cache file has bad mojo"); 315 close(fd); 316 return; 317 } 318 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); 319 if (crc32c(buf + headerSize, cacheSize) != *crc) { 320 ALOGE("cache file failed CRC check"); 321 close(fd); 322 return; 323 } 324 325 int err = mBlobCache->unflatten(buf + headerSize, cacheSize); 326 if (err < 0) { 327 ALOGE("error reading cache contents: %s (%d)", strerror(-err), 328 -err); 329 munmap(buf, fileSize); 330 close(fd); 331 return; 332 } 333 334 munmap(buf, fileSize); 335 close(fd); 336 } 337} 338 339// ---------------------------------------------------------------------------- 340}; // namespace android 341// ---------------------------------------------------------------------------- 342