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#include "egl_display.h" 19#include "egl_impl.h" 20#include "egldefs.h" 21 22#include <fcntl.h> 23#include <sys/mman.h> 24#include <sys/stat.h> 25#include <sys/types.h> 26#include <unistd.h> 27 28#ifndef MAX_EGL_CACHE_ENTRY_SIZE 29#define MAX_EGL_CACHE_ENTRY_SIZE (16 * 1024); 30#endif 31 32#ifndef MAX_EGL_CACHE_KEY_SIZE 33#define MAX_EGL_CACHE_KEY_SIZE (1024); 34#endif 35 36#ifndef MAX_EGL_CACHE_SIZE 37#define MAX_EGL_CACHE_SIZE (64 * 1024); 38#endif 39 40// Cache size limits. 41static const size_t maxKeySize = MAX_EGL_CACHE_KEY_SIZE; 42static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE; 43static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE; 44 45// Cache file header 46static const char* cacheFileMagic = "EGL$"; 47static const size_t cacheFileHeaderSize = 8; 48 49// The time in seconds to wait before saving newly inserted cache entries. 50static const unsigned int deferredSaveDelay = 4; 51 52// ---------------------------------------------------------------------------- 53namespace android { 54// ---------------------------------------------------------------------------- 55 56#define BC_EXT_STR "EGL_ANDROID_blob_cache" 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 mBlobCache(NULL) { 77} 78 79egl_cache_t::~egl_cache_t() { 80} 81 82egl_cache_t egl_cache_t::sCache; 83 84egl_cache_t* egl_cache_t::get() { 85 return &sCache; 86} 87 88void egl_cache_t::initialize(egl_display_t *display) { 89 Mutex::Autolock lock(mMutex); 90 91 egl_connection_t* const cnx = &gEGLImpl; 92 if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { 93 const char* exts = display->disp.queryString.extensions; 94 size_t bcExtLen = strlen(BC_EXT_STR); 95 size_t extsLen = strlen(exts); 96 bool equal = !strcmp(BC_EXT_STR, exts); 97 bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1); 98 bool atEnd = (bcExtLen+1) < extsLen && 99 !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1)); 100 bool inMiddle = strstr(exts, " " BC_EXT_STR " "); 101 if (equal || atStart || atEnd || inMiddle) { 102 PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID; 103 eglSetBlobCacheFuncsANDROID = 104 reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>( 105 cnx->egl.eglGetProcAddress( 106 "eglSetBlobCacheFuncsANDROID")); 107 if (eglSetBlobCacheFuncsANDROID == NULL) { 108 ALOGE("EGL_ANDROID_blob_cache advertised, " 109 "but unable to get eglSetBlobCacheFuncsANDROID"); 110 return; 111 } 112 113 eglSetBlobCacheFuncsANDROID(display->disp.dpy, 114 android::setBlob, android::getBlob); 115 EGLint err = cnx->egl.eglGetError(); 116 if (err != EGL_SUCCESS) { 117 ALOGE("eglSetBlobCacheFuncsANDROID resulted in an error: " 118 "%#x", err); 119 } 120 } 121 } 122 123 mInitialized = true; 124} 125 126void egl_cache_t::terminate() { 127 Mutex::Autolock lock(mMutex); 128 if (mBlobCache != NULL) { 129 saveBlobCacheLocked(); 130 mBlobCache = NULL; 131 } 132 mInitialized = false; 133} 134 135void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, 136 const void* value, EGLsizeiANDROID valueSize) { 137 Mutex::Autolock lock(mMutex); 138 139 if (keySize < 0 || valueSize < 0) { 140 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); 141 return; 142 } 143 144 if (mInitialized) { 145 sp<BlobCache> bc = getBlobCacheLocked(); 146 bc->set(key, keySize, value, valueSize); 147 148 if (!mSavePending) { 149 class DeferredSaveThread : public Thread { 150 public: 151 DeferredSaveThread() : Thread(false) {} 152 153 virtual bool threadLoop() { 154 sleep(deferredSaveDelay); 155 egl_cache_t* c = egl_cache_t::get(); 156 Mutex::Autolock lock(c->mMutex); 157 if (c->mInitialized) { 158 c->saveBlobCacheLocked(); 159 } 160 c->mSavePending = false; 161 return false; 162 } 163 }; 164 165 // The thread will hold a strong ref to itself until it has finished 166 // running, so there's no need to keep a ref around. 167 sp<Thread> deferredSaveThread(new DeferredSaveThread()); 168 mSavePending = true; 169 deferredSaveThread->run(); 170 } 171 } 172} 173 174EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, 175 void* value, EGLsizeiANDROID valueSize) { 176 Mutex::Autolock lock(mMutex); 177 178 if (keySize < 0 || valueSize < 0) { 179 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); 180 return 0; 181 } 182 183 if (mInitialized) { 184 sp<BlobCache> bc = getBlobCacheLocked(); 185 return bc->get(key, keySize, value, valueSize); 186 } 187 return 0; 188} 189 190void egl_cache_t::setCacheFilename(const char* filename) { 191 Mutex::Autolock lock(mMutex); 192 mFilename = filename; 193} 194 195sp<BlobCache> egl_cache_t::getBlobCacheLocked() { 196 if (mBlobCache == NULL) { 197 mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize); 198 loadBlobCacheLocked(); 199 } 200 return mBlobCache; 201} 202 203static uint32_t crc32c(const uint8_t* buf, size_t len) { 204 const uint32_t polyBits = 0x82F63B78; 205 uint32_t r = 0; 206 for (size_t i = 0; i < len; i++) { 207 r ^= buf[i]; 208 for (int j = 0; j < 8; j++) { 209 if (r & 1) { 210 r = (r >> 1) ^ polyBits; 211 } else { 212 r >>= 1; 213 } 214 } 215 } 216 return r; 217} 218 219void egl_cache_t::saveBlobCacheLocked() { 220 if (mFilename.length() > 0) { 221 size_t cacheSize = mBlobCache->getFlattenedSize(); 222 size_t headerSize = cacheFileHeaderSize; 223 const char* fname = mFilename.string(); 224 225 // Try to create the file with no permissions so we can write it 226 // without anyone trying to read it. 227 int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); 228 if (fd == -1) { 229 if (errno == EEXIST) { 230 // The file exists, delete it and try again. 231 if (unlink(fname) == -1) { 232 // No point in retrying if the unlink failed. 233 ALOGE("error unlinking cache file %s: %s (%d)", fname, 234 strerror(errno), errno); 235 return; 236 } 237 // Retry now that we've unlinked the file. 238 fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); 239 } 240 if (fd == -1) { 241 ALOGE("error creating cache file %s: %s (%d)", fname, 242 strerror(errno), errno); 243 return; 244 } 245 } 246 247 size_t fileSize = headerSize + cacheSize; 248 249 uint8_t* buf = new uint8_t [fileSize]; 250 if (!buf) { 251 ALOGE("error allocating buffer for cache contents: %s (%d)", 252 strerror(errno), errno); 253 close(fd); 254 unlink(fname); 255 return; 256 } 257 258 status_t err = mBlobCache->flatten(buf + headerSize, cacheSize, NULL, 259 0); 260 if (err != OK) { 261 ALOGE("error writing cache contents: %s (%d)", strerror(-err), 262 -err); 263 delete [] buf; 264 close(fd); 265 unlink(fname); 266 return; 267 } 268 269 // Write the file magic and CRC 270 memcpy(buf, cacheFileMagic, 4); 271 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); 272 *crc = crc32c(buf + headerSize, cacheSize); 273 274 if (write(fd, buf, fileSize) == -1) { 275 ALOGE("error writing cache file: %s (%d)", strerror(errno), 276 errno); 277 delete [] buf; 278 close(fd); 279 unlink(fname); 280 return; 281 } 282 283 delete [] buf; 284 fchmod(fd, S_IRUSR); 285 close(fd); 286 } 287} 288 289void egl_cache_t::loadBlobCacheLocked() { 290 if (mFilename.length() > 0) { 291 size_t headerSize = cacheFileHeaderSize; 292 293 int fd = open(mFilename.string(), O_RDONLY, 0); 294 if (fd == -1) { 295 if (errno != ENOENT) { 296 ALOGE("error opening cache file %s: %s (%d)", mFilename.string(), 297 strerror(errno), errno); 298 } 299 return; 300 } 301 302 struct stat statBuf; 303 if (fstat(fd, &statBuf) == -1) { 304 ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno); 305 close(fd); 306 return; 307 } 308 309 // Sanity check the size before trying to mmap it. 310 size_t fileSize = statBuf.st_size; 311 if (fileSize > maxTotalSize * 2) { 312 ALOGE("cache file is too large: %#llx", statBuf.st_size); 313 close(fd); 314 return; 315 } 316 317 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize, 318 PROT_READ, MAP_PRIVATE, fd, 0)); 319 if (buf == MAP_FAILED) { 320 ALOGE("error mmaping cache file: %s (%d)", strerror(errno), 321 errno); 322 close(fd); 323 return; 324 } 325 326 // Check the file magic and CRC 327 size_t cacheSize = fileSize - headerSize; 328 if (memcmp(buf, cacheFileMagic, 4) != 0) { 329 ALOGE("cache file has bad mojo"); 330 close(fd); 331 return; 332 } 333 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); 334 if (crc32c(buf + headerSize, cacheSize) != *crc) { 335 ALOGE("cache file failed CRC check"); 336 close(fd); 337 return; 338 } 339 340 status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL, 341 0); 342 if (err != OK) { 343 ALOGE("error reading cache contents: %s (%d)", strerror(-err), 344 -err); 345 munmap(buf, fileSize); 346 close(fd); 347 return; 348 } 349 350 munmap(buf, fileSize); 351 close(fd); 352 } 353} 354 355// ---------------------------------------------------------------------------- 356}; // namespace android 357// ---------------------------------------------------------------------------- 358