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.
43 static const size_t maxKeySize = MAX_EGL_CACHE_KEY_SIZE;
44 static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE;
45 static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE;
46 
47 // Cache file header
48 static const char* cacheFileMagic = "EGL$";
49 static const size_t cacheFileHeaderSize = 8;
50 
51 // The time in seconds to wait before saving newly inserted cache entries.
52 static const unsigned int deferredSaveDelay = 4;
53 
54 // ----------------------------------------------------------------------------
55 namespace android {
56 // ----------------------------------------------------------------------------
57 
58 #define BC_EXT_STR "EGL_ANDROID_blob_cache"
59 
60 //
61 // Callback functions passed to EGL.
62 //
setBlob(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)63 static 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 
getBlob(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)68 static 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 //
egl_cache_t()76 egl_cache_t::egl_cache_t() :
77         mInitialized(false),
78         mBlobCache(NULL) {
79 }
80 
~egl_cache_t()81 egl_cache_t::~egl_cache_t() {
82 }
83 
84 egl_cache_t egl_cache_t::sCache;
85 
get()86 egl_cache_t* egl_cache_t::get() {
87     return &sCache;
88 }
89 
initialize(egl_display_t * display)90 void 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 
terminate()128 void egl_cache_t::terminate() {
129     Mutex::Autolock lock(mMutex);
130     saveBlobCacheLocked();
131     mBlobCache = NULL;
132 }
133 
setBlob(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)134 void 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 
getBlob(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)173 EGLsizeiANDROID 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 
setCacheFilename(const char * filename)189 void egl_cache_t::setCacheFilename(const char* filename) {
190     Mutex::Autolock lock(mMutex);
191     mFilename = filename;
192 }
193 
getBlobCacheLocked()194 sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
195     if (mBlobCache == NULL) {
196         mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
197         loadBlobCacheLocked();
198     }
199     return mBlobCache;
200 }
201 
crc32c(const uint8_t * buf,size_t len)202 static 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 
saveBlobCacheLocked()218 void 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 
loadBlobCacheLocked()287 void 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