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