1 /*
  2  * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package com.sun.glass.ui.monocle;
 26 
 27 import com.sun.glass.ui.monocle.EPDSystem.FbVarScreenInfo;
 28 import com.sun.glass.ui.monocle.EPDSystem.IntStructure;
 29 import com.sun.glass.ui.monocle.EPDSystem.MxcfbUpdateData;
 30 import com.sun.glass.ui.monocle.EPDSystem.MxcfbWaveformModes;
 31 import com.sun.javafx.logging.PlatformLogger;
 32 import com.sun.javafx.logging.PlatformLogger.Level;
 33 import com.sun.javafx.util.Logging;
 34 import java.io.IOException;
 35 import java.nio.ByteBuffer;
 36 import java.text.MessageFormat;
 37 
 38 /**
 39  * Represents the standard Linux frame buffer device interface plus the custom
 40  * extensions to that interface provided by the Electrophoretic Display
 41  * Controller (EPDC) frame buffer driver.
 42  * <p>
 43  * The Linux frame buffer device interface is documented in <cite>The Frame
 44  * Buffer Device API</cite> found in the Ubuntu package called <i>linux-doc</i>
 45  * (see <i>/usr/share/doc/linux-doc/fb/api.txt.gz</i>).</p>
 46  * <p>
 47  * The EPDC frame buffer driver extensions are documented in the <cite>i.MX
 48  * Linux Reference Manual</cite> available on the
 49  * <a href="https://www.nxp.com/">NXP website</a> (registration required). On
 50  * the NXP home page, click Products, ARM Processors, i.MX Application
 51  * Processors, and then i.MX 6 Processors, for example. Select the i.MX6SLL
 52  * Product in the chart; then click the Documentation tab. Look for a download
 53  * with a label for Linux documents, like L4.1.15_2.1.0_LINUX_DOCS, under the
 54  * Supporting Information section. After downloading and expanding the archive,
 55  * the reference manual is found in the <i>doc</i> directory as the file
 56  * <i>i.MX_Linux_Reference_Manual.pdf</i>.</p>
 57  */
 58 class EPDFrameBuffer {
 59 
 60     /**
 61      * The arithmetic right shift value to convert a bit depth to a byte depth.
 62      */
 63     private static final int BITS_TO_BYTES = 3;
 64 
 65     /**
 66      * The delay in milliseconds between the completion of all updates in the
 67      * EPDC driver and when the driver powers down the EPDC and display power
 68      * supplies.
 69      */
 70     private static final int POWERDOWN_DELAY = 1_000;
 71 
 72     /**
 73      * Linux system error: ENOTTY 25 Inappropriate ioctl for device.
 74      */
 75     private static final int ENOTTY = 25;
 76 
 77     private final PlatformLogger logger = Logging.getJavaFXLogger();
 78     private final EPDSettings settings;
 79     private final LinuxSystem system;
 80     private final EPDSystem driver;
 81     private final long fd;
 82 
 83     private final int xres;
 84     private final int yres;
 85     private final int xresVirtual;
 86     private final int yresVirtual;
 87     private final int xoffset;
 88     private final int yoffset;
 89     private final int bitsPerPixel;
 90     private final int bytesPerPixel;
 91     private final int byteOffset;
 92     private final MxcfbUpdateData updateData;
 93     private final MxcfbUpdateData syncUpdate;
 94 
 95     private int updateMarker;
 96     private int lastMarker;
 97 
 98     /**
 99      * Creates a new {@code EPDFrameBuffer} for the given frame buffer device.
100      * The geometry of the Linux frame buffer is shown below for various color
101      * depths and rotations on a sample system, as printed by the <i>fbset</i>
102      * command. The first three are for landscape mode, while the last three are
103      * for portrait.
104      * <pre>{@code
105      * geometry 800 600 800 640 32 (line length: 3200)
106      * geometry 800 600 800 1280 16 (line length: 1600)
107      * geometry 800 600 800 1280 8 (line length: 800)
108      *
109      * geometry 600 800 608 896 32 (line length: 2432)
110      * geometry 600 800 608 1792 16 (line length: 1216)
111      * geometry 600 800 608 1792 8 (line length: 608)
112      * }</pre>
113      *
114      * @implNote {@code MonocleApplication} creates a {@code Screen} which
115      * requires that the width be set to {@link #xresVirtual} even though only
116      * the first {@link #xres} pixels of each row are visible. The EPDC driver
117      * supports panning only in the y-direction, so it is not possible to center
118      * the visible resolution horizontally when these values differ. The JavaFX
119      * application should be left-aligned in this case and ignore the few extra
120      * pixels on the right of its screen.
121      *
122      * @param fbPath the frame buffer device path, such as <i>/dev/fb0</i>
123      * @throws IOException if an error occurs when opening the frame buffer
124      * device or when getting or setting the frame buffer configuration
125      * @throws IllegalArgumentException if the EPD settings specify an
126      * unsupported color depth
127      */
128     EPDFrameBuffer(String fbPath) throws IOException {
129         settings = EPDSettings.newInstance();
130         system = LinuxSystem.getLinuxSystem();
131         driver = EPDSystem.getEPDSystem();
132         fd = system.open(fbPath, LinuxSystem.O_RDWR);
133         if (fd == -1) {
134             throw new IOException(system.getErrorMessage());
135         }
136 
137         /*
138          * Gets the current settings of the frame buffer device.
139          */
140         var screen = new FbVarScreenInfo();
141         getScreenInfo(screen);
142 
143         /*
144          * Changes the settings of the frame buffer from the system properties.
145          *
146          * See the section, "Format configuration," in "The Frame Buffer Device
147          * API" for details. Note that xoffset is always zero, and yoffset can
148          * be modified only by panning in the y-direction with the IOCTL call to
149          * LinuxSystem.FBIOPAN_DISPLAY.
150          */
151         screen.setBitsPerPixel(screen.p, settings.bitsPerPixel);
152         screen.setGrayscale(screen.p, settings.grayscale);
153         switch (settings.bitsPerPixel) {
154             case Byte.SIZE:
155                 // rgba 8/0,8/0,8/0,0/0 (set by driver when grayscale > 0)
156                 screen.setRed(screen.p, 0, 0);
157                 screen.setGreen(screen.p, 0, 0);
158                 screen.setBlue(screen.p, 0, 0);
159                 screen.setTransp(screen.p, 0, 0);
160                 break;
161             case Short.SIZE:
162                 // rgba 5/11,6/5,5/0,0/0
163                 screen.setRed(screen.p, 5, 11);
164                 screen.setGreen(screen.p, 6, 5);
165                 screen.setBlue(screen.p, 5, 0);
166                 screen.setTransp(screen.p, 0, 0);
167                 break;
168             case Integer.SIZE:
169                 // rgba 8/16,8/8,8/0,8/24
170                 screen.setRed(screen.p, 8, 16);
171                 screen.setGreen(screen.p, 8, 8);
172                 screen.setBlue(screen.p, 8, 0);
173                 screen.setTransp(screen.p, 8, 24);
174                 break;
175             default:
176                 String msg = MessageFormat.format("Unsupported color depth: {0} bpp", settings.bitsPerPixel);
177                 logger.severe(msg);
178                 throw new IllegalArgumentException(msg);
179         }
180         screen.setActivate(screen.p, EPDSystem.FB_ACTIVATE_FORCE);
181         screen.setRotate(screen.p, settings.rotate);
182         setScreenInfo(screen);
183 
184         /*
185          * Gets and logs the new settings of the frame buffer device.
186          */
187         getScreenInfo(screen);
188         logScreenInfo(screen);
189         xres = screen.getXRes(screen.p);
190         yres = screen.getYRes(screen.p);
191         xresVirtual = screen.getXResVirtual(screen.p);
192         yresVirtual = screen.getYResVirtual(screen.p);
193         xoffset = screen.getOffsetX(screen.p);
194         yoffset = screen.getOffsetY(screen.p);
195         bitsPerPixel = screen.getBitsPerPixel(screen.p);
196         bytesPerPixel = bitsPerPixel >>> BITS_TO_BYTES;
197         byteOffset = (xoffset + yoffset * xresVirtual) * bytesPerPixel;
198 
199         /*
200          * Allocates objects for reuse to avoid creating new direct byte buffers
201          * outside of the Java heap on each display update.
202          */
203         updateData = new MxcfbUpdateData();
204         syncUpdate = createDefaultUpdate(xres, yres);
205     }
206 
207     /**
208      * Gets the variable screen information of the frame buffer. Run the
209      * <i>fbset</i> command as <i>root</i> to print the screen information.
210      *
211      * @param screen the object representing the variable screen information
212      * @throws IOException if an error occurs getting the information
213      */
214     private void getScreenInfo(FbVarScreenInfo screen) throws IOException {
215         int rc = system.ioctl(fd, LinuxSystem.FBIOGET_VSCREENINFO, screen.p);
216         if (rc != 0) {
217             system.close(fd);
218             throw new IOException(system.getErrorMessage());
219         }
220     }
221 
222     /**
223      * Sets the variable screen information of the frame buffer.
224      * <p>
225      * "To ensure that the EPDC driver receives the initialization request, the
226      * {@code activate} field of the {@code fb_var_screeninfo} parameter should
227      * be set to {@code FB_ACTIVATE_FORCE}." [EPDC Panel Initialization,
228      * <cite>i.MX Linux Reference Manual</cite>]</p>
229      * <p>
230      * To request a change to 8-bit grayscale format, the bits per pixel must be
231      * set to 8 and the grayscale value must be set to one of the two valid
232      * grayscale format values: {@code GRAYSCALE_8BIT} or
233      * {@code GRAYSCALE_8BIT_INVERTED}. [Grayscale Framebuffer Selection,
234      * <cite>i.MX Linux Reference Manual</cite>]</p>
235      *
236      * @param screen the object representing the variable screen information
237      * @throws IOException if an error occurs setting the information
238      */
239     private void setScreenInfo(FbVarScreenInfo screen) throws IOException {
240         int rc = system.ioctl(fd, LinuxSystem.FBIOPUT_VSCREENINFO, screen.p);
241         if (rc != 0) {
242             system.close(fd);
243             throw new IOException(system.getErrorMessage());
244         }
245     }
246 
247     /**
248      * Logs the variable screen information of the frame buffer, depending on
249      * the logging level.
250      *
251      * @param screen the object representing the variable screen information
252      */
253     private void logScreenInfo(FbVarScreenInfo screen) {
254         if (logger.isLoggable(Level.FINE)) {
255             logger.fine("Frame buffer geometry: {0} {1} {2} {3} {4}",
256                     screen.getXRes(screen.p), screen.getYRes(screen.p),
257                     screen.getXResVirtual(screen.p), screen.getYResVirtual(screen.p),
258                     screen.getBitsPerPixel(screen.p));
259             logger.fine("Frame buffer rgba: {0}/{1},{2}/{3},{4}/{5},{6}/{7}",
260                     screen.getRedLength(screen.p), screen.getRedOffset(screen.p),
261                     screen.getGreenLength(screen.p), screen.getGreenOffset(screen.p),
262                     screen.getBlueLength(screen.p), screen.getBlueOffset(screen.p),
263                     screen.getTranspLength(screen.p), screen.getTranspOffset(screen.p));
264             logger.fine("Frame buffer grayscale: {0}", screen.getGrayscale(screen.p));
265         }
266     }
267 
268     /**
269      * Creates the default update data with values from the EPD system
270      * properties, setting all fields except for the update marker. Reusing the
271      * update data object avoids creating a new one for each update request.
272      *
273      * @implNote An update mode of {@link EPDSystem#UPDATE_MODE_FULL} would make
274      * the {@link EPDSettings#NO_WAIT} system property useless by changing all
275      * non-colliding updates into colliding ones, so this method sets the
276      * default update mode to {@link EPDSystem#UPDATE_MODE_PARTIAL}.
277      *
278      * @param width the width of the update region
279      * @param height the height of the update region
280      * @return the default update data with all fields set but the update marker
281      */
282     private MxcfbUpdateData createDefaultUpdate(int width, int height) {
283         var update = new MxcfbUpdateData();
284         update.setUpdateRegion(update.p, 0, 0, width, height);
285         update.setWaveformMode(update.p, settings.waveformMode);
286         update.setUpdateMode(update.p, EPDSystem.UPDATE_MODE_PARTIAL);
287         update.setTemp(update.p, EPDSystem.TEMP_USE_AMBIENT);
288         update.setFlags(update.p, settings.flags);
289         return update;
290     }
291 
292     /**
293      * Defines a mapping for common waveform modes. This mapping must be
294      * configured for the automatic waveform mode selection to function
295      * properly. Each of the parameters should be set to one of the following:
296      * <ul>
297      * <li>{@link EPDSystem#WAVEFORM_MODE_INIT}</li>
298      * <li>{@link EPDSystem#WAVEFORM_MODE_DU}</li>
299      * <li>{@link EPDSystem#WAVEFORM_MODE_GC16}</li>
300      * <li>{@link EPDSystem#WAVEFORM_MODE_GC4}</li>
301      * <li>{@link EPDSystem#WAVEFORM_MODE_A2}</li>
302      * </ul>
303      *
304      * @implNote This method fails on the Kobo Glo HD Model N437 with the error
305      * ENOTTY (25), "Inappropriate ioctl for device." The driver on that device
306      * uses an extended structure with four additional integers, changing its
307      * size and its corresponding request code. This method could use the
308      * extended structure, but the driver on the Kobo Glo HD ignores it and
309      * returns immediately, anyway. Furthermore, newer devices support both the
310      * current structure and the extended one, but define the extra fields in a
311      * different order. Therefore, simply use the current structure and ignore
312      * an error of ENOTTY, picking up the default values for any extra fields.
313      *
314      * @param init the initialization mode for clearing the screen to all white
315      * @param du the direct update mode for changing any gray values to either
316      * all black or all white
317      * @param gc4 the mode for 4-level (2-bit) grayscale images and text
318      * @param gc8 the mode for 8-level (3-bit) grayscale images and text
319      * @param gc16 the mode for 16-level (4-bit) grayscale images and text
320      * @param gc32 the mode for 32-level (5-bit) grayscale images and text
321      */
322     private void setWaveformModes(int init, int du, int gc4, int gc8, int gc16, int gc32) {
323         var modes = new MxcfbWaveformModes();
324         modes.setModes(modes.p, init, du, gc4, gc8, gc16, gc32);
325         int rc = system.ioctl(fd, driver.MXCFB_SET_WAVEFORM_MODES, modes.p);
326         if (rc != 0 && system.errno() != ENOTTY) {
327             logger.severe("Failed setting waveform modes: {0} ({1})",
328                     system.getErrorMessage(), system.errno());
329         }
330     }
331 
332     /**
333      * Sets the temperature to be used by the EPDC driver in subsequent panel
334      * updates. Note that this temperature setting may be overridden by setting
335      * the temperature in a specific update to anything other than
336      * {@link EPDSystem#TEMP_USE_AMBIENT}.
337      *
338      * @param temp the temperature in degrees Celsius
339      */
340     private void setTemperature(int temp) {
341         int rc = driver.ioctl(fd, driver.MXCFB_SET_TEMPERATURE, temp);
342         if (rc != 0) {
343             logger.severe("Failed setting temperature to {2} degrees Celsius: {0} ({1})",
344                     system.getErrorMessage(), system.errno(), temp);
345         }
346     }
347 
348     /**
349      * Selects between automatic and region update mode. In region update mode,
350      * updates must be submitted with an IOCTL call to
351      * {@link EPDSystem#MXCFB_SEND_UPDATE}. In automatic mode, updates are
352      * generated by the driver when it detects that pages in a frame buffer
353      * memory region have been modified.
354      * <p>
355      * Automatic mode is available only when it has been enabled in the Linux
356      * kernel by the option CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE. You can find
357      * the configuration options used to build the kernel in a file under
358      * <i>/proc</i> or <i>/boot</i>, such as <i>/proc/config.gz</i>.</p>
359      *
360      * @param mode the automatic update mode, one of:
361      * <ul>
362      * <li>{@link EPDSystem#AUTO_UPDATE_MODE_REGION_MODE}</li>
363      * <li>{@link EPDSystem#AUTO_UPDATE_MODE_AUTOMATIC_MODE}</li>
364      * </ul>
365      */
366     private void setAutoUpdateMode(int mode) {
367         int rc = driver.ioctl(fd, driver.MXCFB_SET_AUTO_UPDATE_MODE, mode);
368         if (rc != 0) {
369             logger.severe("Failed setting auto-update mode to {2}: {0} ({1})",
370                     system.getErrorMessage(), system.errno(), mode);
371         }
372     }
373 
374     /**
375      * Requests the entire visible region of the frame buffer to be updated to
376      * the display.
377      *
378      * @param updateMode the update mode, one of:
379      * <ul>
380      * <li>{@link EPDSystem#UPDATE_MODE_PARTIAL}</li>
381      * <li>{@link EPDSystem#UPDATE_MODE_FULL}</li>
382      * </ul>
383      * @param waveformMode the waveform mode, one of:
384      * <ul>
385      * <li>{@link EPDSystem#WAVEFORM_MODE_INIT}</li>
386      * <li>{@link EPDSystem#WAVEFORM_MODE_DU}</li>
387      * <li>{@link EPDSystem#WAVEFORM_MODE_GC16}</li>
388      * <li>{@link EPDSystem#WAVEFORM_MODE_GC4}</li>
389      * <li>{@link EPDSystem#WAVEFORM_MODE_A2}</li>
390      * <li>{@link EPDSystem#WAVEFORM_MODE_AUTO}</li>
391      * </ul>
392      * @param flags a bit mask composed of the following flag values:
393      * <ul>
394      * <li>{@link EPDSystem#EPDC_FLAG_ENABLE_INVERSION}</li>
395      * <li>{@link EPDSystem#EPDC_FLAG_FORCE_MONOCHROME}</li>
396      * <li>{@link EPDSystem#EPDC_FLAG_USE_DITHERING_Y1}</li>
397      * <li>{@link EPDSystem#EPDC_FLAG_USE_DITHERING_Y4}</li>
398      * </ul>
399      * @return the marker to identify this update in a subsequence call to
400      * {@link #waitForUpdateComplete}
401      */
402     private int sendUpdate(int updateMode, int waveformMode, int flags) {
403         updateData.setUpdateRegion(updateData.p, 0, 0, xres, yres);
404         updateData.setUpdateMode(updateData.p, updateMode);
405         updateData.setTemp(updateData.p, EPDSystem.TEMP_USE_AMBIENT);
406         updateData.setFlags(updateData.p, flags);
407         return sendUpdate(updateData, waveformMode);
408     }
409 
410     /**
411      * Requests an update to the display, allowing for the reuse of the update
412      * data object. The waveform mode is reset because the update data could
413      * have been used in a previous update. In that case, the waveform mode may
414      * have been modified by the EPDC driver with the actual mode selected. The
415      * update marker is overwritten with the next sequential marker.
416      *
417      * @param update the data describing the update; the waveform mode and
418      * update marker are overwritten
419      * @param waveformMode the waveform mode for this update
420      * @return the marker to identify this update in a subsequence call to
421      * {@link #waitForUpdateComplete}
422      */
423     private int sendUpdate(MxcfbUpdateData update, int waveformMode) {
424         /*
425          * The IOCTL call to MXCFB_WAIT_FOR_UPDATE_COMPLETE returns the error
426          * "Invalid argument (22)" when passed an update marker of zero.
427          */
428         updateMarker++;
429         if (updateMarker == 0) {
430             updateMarker++;
431         }
432         update.setWaveformMode(update.p, waveformMode);
433         update.setUpdateMarker(update.p, updateMarker);
434         int rc = system.ioctl(fd, driver.MXCFB_SEND_UPDATE, update.p);
435         if (rc != 0) {
436             logger.severe("Failed sending update {2}: {0} ({1})",
437                     system.getErrorMessage(), system.errno(), Integer.toUnsignedLong(updateMarker));
438         } else if (logger.isLoggable(Level.FINER)) {
439             logger.finer("Sent update: {0} x {1}, waveform {2}, selected {3}, flags 0x{4}, marker {5}",
440                     update.getUpdateRegionWidth(update.p), update.getUpdateRegionHeight(update.p),
441                     waveformMode, update.getWaveformMode(update.p),
442                     Integer.toHexString(update.getFlags(update.p)).toUpperCase(),
443                     Integer.toUnsignedLong(updateMarker));
444         }
445         return updateMarker;
446     }
447 
448     /**
449      * Blocks and waits for a previous update request to complete.
450      *
451      * @param marker the marker to identify a particular update, returned by
452      * {@link #sendUpdate(MxcfbUpdateData, int)}
453      */
454     private void waitForUpdateComplete(int marker) {
455         /*
456          * This IOCTL call returns: 0 if the marker was not found because the
457          * update already completed or failed, negative (-1) with the error
458          * "Connection timed out (110)" if the wait timed out after 5 seconds,
459          * or positive if the wait occurred and completed (see
460          * "wait_for_completion_timeout" in "kernel/sched/completion.c").
461          */
462         int rc = driver.ioctl(fd, driver.MXCFB_WAIT_FOR_UPDATE_COMPLETE, marker);
463         if (rc < 0) {
464             logger.severe("Failed waiting for update {2}: {0} ({1})",
465                     system.getErrorMessage(), system.errno(), Integer.toUnsignedLong(marker));
466         } else if (rc == 0 && logger.isLoggable(Level.FINER)) {
467             logger.finer("Update completed before wait: marker {0}",
468                     Integer.toUnsignedLong(marker));
469         }
470     }
471 
472     /**
473      * Sets the delay between the completion of all updates in the driver and
474      * when the driver should power down the EPDC and display power supplies. To
475      * disable powering down entirely, use the delay value
476      * {@link EPDSystem#FB_POWERDOWN_DISABLE}.
477      *
478      * @param delay the delay in milliseconds
479      */
480     private void setPowerdownDelay(int delay) {
481         int rc = driver.ioctl(fd, driver.MXCFB_SET_PWRDOWN_DELAY, delay);
482         if (rc != 0) {
483             logger.severe("Failed setting power-down delay to {2}: {0} ({1})",
484                     system.getErrorMessage(), system.errno(), delay);
485         }
486     }
487 
488     /**
489      * Gets the current power-down delay from the EPDC driver.
490      *
491      * @return the delay in milliseconds
492      */
493     private int getPowerdownDelay() {
494         var integer = new IntStructure();
495         int rc = system.ioctl(fd, driver.MXCFB_GET_PWRDOWN_DELAY, integer.p);
496         if (rc != 0) {
497             logger.severe("Failed getting power-down delay: {0} ({1})",
498                     system.getErrorMessage(), system.errno());
499         }
500         return integer.get(integer.p);
501     }
502 
503     /**
504      * Selects a scheme for the flow of updates within the driver.
505      *
506      * @param scheme the update scheme, one of:
507      * <ul>
508      * <li>{@link EPDSystem#UPDATE_SCHEME_SNAPSHOT}</li>
509      * <li>{@link EPDSystem#UPDATE_SCHEME_QUEUE}</li>
510      * <li>{@link EPDSystem#UPDATE_SCHEME_QUEUE_AND_MERGE}</li>
511      * </ul>
512      */
513     private void setUpdateScheme(int scheme) {
514         int rc = driver.ioctl(fd, driver.MXCFB_SET_UPDATE_SCHEME, scheme);
515         if (rc != 0) {
516             logger.severe("Failed setting update scheme to {2}: {0} ({1})",
517                     system.getErrorMessage(), system.errno(), scheme);
518         }
519     }
520 
521     /**
522      * Initializes the EPDC frame buffer device, setting the update scheme to
523      * {@link EPDSystem#UPDATE_SCHEME_SNAPSHOT}.
524      */
525     void init() {
526         setWaveformModes(EPDSystem.WAVEFORM_MODE_INIT, EPDSystem.WAVEFORM_MODE_DU,
527                 EPDSystem.WAVEFORM_MODE_GC4, EPDSystem.WAVEFORM_MODE_GC16,
528                 EPDSystem.WAVEFORM_MODE_GC16, EPDSystem.WAVEFORM_MODE_GC16);
529         setTemperature(EPDSystem.TEMP_USE_AMBIENT);
530         setAutoUpdateMode(EPDSystem.AUTO_UPDATE_MODE_REGION_MODE);
531         setPowerdownDelay(POWERDOWN_DELAY);
532         setUpdateScheme(EPDSystem.UPDATE_SCHEME_SNAPSHOT);
533     }
534 
535     /**
536      * Clears the display panel. The visible frame buffer should be cleared with
537      * zeros when called. This method sends two direct updates (all black
538      * followed by all white) to refresh the screen and clear any ghosting
539      * effects, and returns when both updates are complete.
540      * <p>
541      * <strong>This method is not thread safe</strong>, but it is invoked only
542      * once from the Event Thread during initialization.</p>
543      */
544     void clear() {
545         lastMarker = sendUpdate(EPDSystem.UPDATE_MODE_FULL,
546                 EPDSystem.WAVEFORM_MODE_DU, 0);
547         lastMarker = sendUpdate(EPDSystem.UPDATE_MODE_FULL,
548                 EPDSystem.WAVEFORM_MODE_DU, EPDSystem.EPDC_FLAG_ENABLE_INVERSION);
549         waitForUpdateComplete(lastMarker);
550     }
551 
552     /**
553      * Sends the updated contents of the Linux frame buffer to the EPDC driver,
554      * optionally synchronizing with the driver by first waiting for the
555      * previous update to complete.
556      * <p>
557      * <strong>This method is not thread safe</strong>, but it is invoked only
558      * from the JavaFX Application Thread.</p>
559      */
560     void sync() {
561         if (!settings.noWait) {
562             waitForUpdateComplete(lastMarker);
563         }
564         lastMarker = sendUpdate(syncUpdate, settings.waveformMode);
565     }
566 
567     /**
568      * Gets the number of bytes from the beginning of the frame buffer to the
569      * start of its visible resolution.
570      *
571      * @return the offset in bytes
572      */
573     int getByteOffset() {
574         return byteOffset;
575     }
576 
577     /**
578      * Creates an off-screen byte buffer equal in resolution to the virtual
579      * resolution of the frame buffer, but with 32 bits per pixel.
580      *
581      * @return a 32-bit pixel buffer matching the resolution of the frame buffer
582      */
583     ByteBuffer getOffscreenBuffer() {
584         /*
585          * In this case, a direct byte buffer outside of the normal heap is
586          * faster than a non-direct byte buffer on the heap. The frame rate is
587          * roughly 10 to 40 percent faster for a framebuffer with 8 bits per
588          * pixel and 40 to 60 percent faster for a framebuffer with 16 bits per
589          * pixel, depending on the device processor and screen size.
590          */
591         int size = xresVirtual * yres * Integer.BYTES;
592         return ByteBuffer.allocateDirect(size);
593     }
594 
595     /**
596      * Creates a new mapping of the Linux frame buffer device into memory.
597      *
598      * @implNote The virtual y-resolution reported by the device driver can be
599      * wrong, as shown by the following example on the Kobo Glo HD Model N437
600      * which reports 2,304 pixels when the correct value is 1,152 pixels
601      * (6,782,976 / 5,888). Therefore, this method cannot use the frame buffer
602      * virtual resolution to calculate its size.
603      *
604      * <pre>{@code
605      * $ sudo fbset -i
606      *
607      * mode "1448x1072-46"
608      * # D: 80.000 MHz, H: 50.188 kHz, V: 46.385 Hz
609      * geometry 1448 1072 1472 2304 32
610      * timings 12500 16 102 4 4 28 2
611      * rgba 8/16,8/8,8/0,8/24
612      * endmode
613      *
614      * Frame buffer device information:
615      * Name        : mxc_epdc_fb
616      * Address     : 0x88000000
617      * Size        : 6782976
618      * Type        : PACKED PIXELS
619      * Visual      : TRUECOLOR
620      * XPanStep    : 1
621      * YPanStep    : 1
622      * YWrapStep   : 0
623      * LineLength  : 5888
624      * Accelerator : No
625      * }</pre>
626      *
627      * @return a byte buffer containing the mapping of the Linux frame buffer
628      * device if successful; otherwise {@code null}
629      */
630     ByteBuffer getMappedBuffer() {
631         ByteBuffer buffer = null;
632         int size = xresVirtual * yres * bytesPerPixel;
633         logger.fine("Mapping frame buffer: {0} bytes", size);
634         long addr = system.mmap(0l, size, LinuxSystem.PROT_WRITE, LinuxSystem.MAP_SHARED, fd, 0);
635         if (addr == LinuxSystem.MAP_FAILED) {
636             logger.severe("Failed mapping {2} bytes of frame buffer: {0} ({1})",
637                     system.getErrorMessage(), system.errno(), size);
638         } else {
639             buffer = C.getC().NewDirectByteBuffer(addr, size);
640         }
641         return buffer;
642     }
643 
644     /**
645      * Deletes the mapping of the Linux frame buffer device.
646      *
647      * @param buffer the byte buffer containing the mapping of the Linux frame
648      * buffer device
649      */
650     void releaseMappedBuffer(ByteBuffer buffer) {
651         int size = buffer.capacity();
652         logger.fine("Unmapping frame buffer: {0} bytes", size);
653         int rc = system.munmap(C.getC().GetDirectBufferAddress(buffer), size);
654         if (rc != 0) {
655             logger.severe("Failed unmapping {2} bytes of frame buffer: {0} ({1})",
656                     system.getErrorMessage(), system.errno(), size);
657         }
658     }
659 
660     /**
661      * Closes the Linux frame buffer device.
662      */
663     void close() {
664         system.close(fd);
665     }
666 
667     /**
668      * Gets the native handle to the Linux frame buffer device.
669      *
670      * @return the frame buffer device file descriptor
671      */
672     long getNativeHandle() {
673         return fd;
674     }
675 
676     /**
677      * Gets the frame buffer width in pixels. See the notes for the
678      * {@linkplain EPDFrameBuffer#EPDFrameBuffer constructor} above.
679      *
680      * @implNote When using an 8-bit, unrotated, and uninverted frame buffer in
681      * the Y8 pixel format, the Kobo Clara HD Model N249 works only when this
682      * method returns the visible x-resolution ({@code xres}) instead of the
683      * normal virtual x-resolution ({@code xresVirtual}).
684      *
685      * @return the width in pixels
686      */
687     int getWidth() {
688         return settings.getWidthVisible ? xres : xresVirtual;
689     }
690 
691     /**
692      * Gets the frame buffer height in pixels.
693      *
694      * @return the height in pixels
695      */
696     int getHeight() {
697         return yres;
698     }
699 
700     /**
701      * Gets the frame buffer color depth in bits per pixel.
702      *
703      * @return the color depth in bits per pixel
704      */
705     int getBitDepth() {
706         return bitsPerPixel;
707     }
708 
709     @Override
710     public String toString() {
711         return MessageFormat.format("{0}[width={1} height={2} bitDepth={3}]",
712                 getClass().getName(), getWidth(), getHeight(), getBitDepth());
713     }
714 }