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