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