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