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