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