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