blob: 73645f70b6cb4d38cd963b9803a2ee1408be27d3 [file] [log] [blame]
Simon Glassc8925112023-06-01 10:23:02 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Implementation of configuration editor
4 *
5 * Copyright 2023 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
Simon Glass6a5af5f2023-08-14 16:40:27 -06009#define LOG_CATEGORY LOGC_EXPO
10
Simon Glassc8925112023-06-01 10:23:02 -060011#include <common.h>
Simon Glass28bf4352023-08-14 16:40:33 -060012#include <abuf.h>
Simon Glass8df4a4e2023-08-14 16:40:25 -060013#include <cedit.h>
Simon Glassc8925112023-06-01 10:23:02 -060014#include <cli.h>
15#include <dm.h>
Simon Glass237f3752023-08-14 16:40:35 -060016#include <env.h>
Simon Glassc8925112023-06-01 10:23:02 -060017#include <expo.h>
Simon Glass2b91ca62023-08-14 16:40:37 -060018#include <malloc.h>
Simon Glassc8925112023-06-01 10:23:02 -060019#include <menu.h>
Simon Glass2b91ca62023-08-14 16:40:37 -060020#include <rtc.h>
Simon Glassc8925112023-06-01 10:23:02 -060021#include <video.h>
22#include <linux/delay.h>
23#include "scene_internal.h"
24
Simon Glass2b91ca62023-08-14 16:40:37 -060025enum {
26 CMOS_MAX_BITS = 2048,
27 CMOS_MAX_BYTES = CMOS_MAX_BITS / 8,
28};
29
30#define CMOS_BYTE(bit) ((bit) / 8)
31#define CMOS_BIT(bit) ((bit) % 8)
32
Simon Glass28bf4352023-08-14 16:40:33 -060033/**
34 * struct cedit_iter_priv - private data for cedit operations
35 *
36 * @buf: Buffer to use when writing settings to the devicetree
Simon Glassb1cd32b2023-08-14 16:40:34 -060037 * @node: Node to read from when reading settings from devicetree
Simon Glass237f3752023-08-14 16:40:35 -060038 * @verbose: true to show writing to environment variables
Simon Glass2b91ca62023-08-14 16:40:37 -060039 * @mask: Mask bits for the CMOS RAM. If a bit is set the byte containing it
40 * will be written
41 * @value: Value bits for CMOS RAM. This is the actual value written
Simon Glass4462fa32023-08-14 16:40:38 -060042 * @dev: RTC device to write to
Simon Glass28bf4352023-08-14 16:40:33 -060043 */
44struct cedit_iter_priv {
45 struct abuf *buf;
Simon Glassb1cd32b2023-08-14 16:40:34 -060046 ofnode node;
Simon Glass237f3752023-08-14 16:40:35 -060047 bool verbose;
Simon Glass2b91ca62023-08-14 16:40:37 -060048 u8 *mask;
49 u8 *value;
Simon Glass4462fa32023-08-14 16:40:38 -060050 struct udevice *dev;
Simon Glass28bf4352023-08-14 16:40:33 -060051};
52
Simon Glassc8925112023-06-01 10:23:02 -060053int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
54{
55 struct scene_obj_txt *txt;
56 struct scene_obj *obj;
57 struct scene *scn;
58 int y;
59
60 scn = expo_lookup_scene_id(exp, scene_id);
61 if (!scn)
62 return log_msg_ret("scn", -ENOENT);
63
64 txt = scene_obj_find_by_name(scn, "prompt");
65 if (txt)
66 scene_obj_set_pos(scn, txt->obj.id, 0, vpriv->ysize - 50);
67
68 txt = scene_obj_find_by_name(scn, "title");
69 if (txt)
70 scene_obj_set_pos(scn, txt->obj.id, 200, 10);
71
72 y = 100;
73 list_for_each_entry(obj, &scn->obj_head, sibling) {
74 if (obj->type == SCENEOBJT_MENU) {
75 scene_obj_set_pos(scn, obj->id, 50, y);
76 scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
77 y += 50;
78 }
79 }
80
81 return 0;
82}
83
Simon Glass6a5af5f2023-08-14 16:40:27 -060084int cedit_prepare(struct expo *exp, struct video_priv **vid_privp,
85 struct scene **scnp)
Simon Glassc8925112023-06-01 10:23:02 -060086{
Simon Glassc8925112023-06-01 10:23:02 -060087 struct video_priv *vid_priv;
Simon Glassc8925112023-06-01 10:23:02 -060088 struct udevice *dev;
89 struct scene *scn;
Simon Glass6a5af5f2023-08-14 16:40:27 -060090 uint scene_id;
Simon Glassc8925112023-06-01 10:23:02 -060091 int ret;
92
Simon Glassc8925112023-06-01 10:23:02 -060093 /* For now we only support a video console */
94 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
95 if (ret)
96 return log_msg_ret("vid", ret);
97 ret = expo_set_display(exp, dev);
98 if (ret)
99 return log_msg_ret("dis", ret);
100
101 ret = expo_first_scene_id(exp);
102 if (ret < 0)
103 return log_msg_ret("scn", ret);
104 scene_id = ret;
105
106 ret = expo_set_scene_id(exp, scene_id);
107 if (ret)
108 return log_msg_ret("sid", ret);
109
110 exp->popup = true;
111
112 /* This is not supported for now */
113 if (0)
114 expo_set_text_mode(exp, true);
115
116 vid_priv = dev_get_uclass_priv(dev);
117
118 scn = expo_lookup_scene_id(exp, scene_id);
119 scene_highlight_first(scn);
120
121 cedit_arange(exp, vid_priv, scene_id);
122
123 ret = expo_calc_dims(exp);
124 if (ret)
125 return log_msg_ret("dim", ret);
126
Simon Glass6a5af5f2023-08-14 16:40:27 -0600127 *vid_privp = vid_priv;
128 *scnp = scn;
129
130 return scene_id;
131}
132
133int cedit_run(struct expo *exp)
134{
135 struct cli_ch_state s_cch, *cch = &s_cch;
136 struct video_priv *vid_priv;
137 uint scene_id;
138 struct scene *scn;
139 bool done;
140 int ret;
141
142 cli_ch_init(cch);
143 ret = cedit_prepare(exp, &vid_priv, &scn);
144 if (ret < 0)
145 return log_msg_ret("prep", ret);
146 scene_id = ret;
147
Simon Glassc8925112023-06-01 10:23:02 -0600148 done = false;
149 do {
150 struct expo_action act;
151 int ichar, key;
152
153 ret = expo_render(exp);
154 if (ret)
155 break;
156
157 ichar = cli_ch_process(cch, 0);
158 if (!ichar) {
159 while (!ichar && !tstc()) {
160 schedule();
161 mdelay(2);
162 ichar = cli_ch_process(cch, -ETIMEDOUT);
163 }
164 if (!ichar) {
165 ichar = getchar();
166 ichar = cli_ch_process(cch, ichar);
167 }
168 }
169
170 key = 0;
171 if (ichar) {
172 key = bootmenu_conv_key(ichar);
173 if (key == BKEY_NONE)
174 key = ichar;
175 }
176 if (!key)
177 continue;
178
179 ret = expo_send_key(exp, key);
180 if (ret)
181 break;
182
183 ret = expo_action_get(exp, &act);
184 if (!ret) {
185 switch (act.type) {
186 case EXPOACT_POINT_OBJ:
187 scene_set_highlight_id(scn, act.select.id);
188 cedit_arange(exp, vid_priv, scene_id);
189 break;
190 case EXPOACT_OPEN:
191 scene_set_open(scn, act.select.id, true);
192 cedit_arange(exp, vid_priv, scene_id);
193 break;
194 case EXPOACT_CLOSE:
195 scene_set_open(scn, act.select.id, false);
196 cedit_arange(exp, vid_priv, scene_id);
197 break;
198 case EXPOACT_SELECT:
199 scene_set_open(scn, scn->highlight_id, false);
200 cedit_arange(exp, vid_priv, scene_id);
201 break;
202 case EXPOACT_QUIT:
203 log_debug("quitting\n");
204 done = true;
205 break;
206 default:
207 break;
208 }
209 }
210 } while (!done);
211
212 if (ret)
213 return log_msg_ret("end", ret);
214
215 return 0;
216}
Simon Glass28bf4352023-08-14 16:40:33 -0600217
218static int check_space(int ret, struct abuf *buf)
219{
220 if (ret == -FDT_ERR_NOSPACE) {
221 if (!abuf_realloc_inc(buf, CEDIT_SIZE_INC))
222 return log_msg_ret("spc", -ENOMEM);
223 ret = fdt_resize(abuf_data(buf), abuf_data(buf),
224 abuf_size(buf));
225 if (ret)
226 return log_msg_ret("res", -EFAULT);
227 }
228
229 return 0;
230}
231
Simon Glass237f3752023-08-14 16:40:35 -0600232static int get_cur_menuitem_text(const struct scene_obj_menu *menu,
233 const char **strp)
234{
235 struct scene *scn = menu->obj.scene;
236 const struct scene_menitem *mi;
237 const struct scene_obj_txt *txt;
238 const char *str;
239
240 mi = scene_menuitem_find(menu, menu->cur_item_id);
241 if (!mi)
242 return log_msg_ret("mi", -ENOENT);
243
244 txt = scene_obj_find(scn, mi->label_id, SCENEOBJT_TEXT);
245 if (!txt)
246 return log_msg_ret("txt", -ENOENT);
247
248 str = expo_get_str(scn->expo, txt->str_id);
249 if (!str)
250 return log_msg_ret("str", -ENOENT);
251 *strp = str;
252
253 return 0;
254}
255
Simon Glass28bf4352023-08-14 16:40:33 -0600256static int h_write_settings(struct scene_obj *obj, void *vpriv)
257{
258 struct cedit_iter_priv *priv = vpriv;
259 struct abuf *buf = priv->buf;
260
261 switch (obj->type) {
262 case SCENEOBJT_NONE:
263 case SCENEOBJT_IMAGE:
264 case SCENEOBJT_TEXT:
265 break;
266 case SCENEOBJT_MENU: {
267 const struct scene_obj_menu *menu;
Simon Glass28bf4352023-08-14 16:40:33 -0600268 const char *str;
269 char name[80];
270 int ret, i;
271
272 menu = (struct scene_obj_menu *)obj;
273 ret = -EAGAIN;
274 for (i = 0; ret && i < 2; i++) {
275 ret = fdt_property_u32(abuf_data(buf), obj->name,
276 menu->cur_item_id);
277 if (!i) {
278 ret = check_space(ret, buf);
279 if (ret)
280 return log_msg_ret("res", -ENOMEM);
281 }
282 }
283 /* this should not happen */
284 if (ret)
285 return log_msg_ret("wrt", -EFAULT);
286
Simon Glass237f3752023-08-14 16:40:35 -0600287 ret = get_cur_menuitem_text(menu, &str);
288 if (ret)
289 return log_msg_ret("mis", ret);
Simon Glass28bf4352023-08-14 16:40:33 -0600290
291 snprintf(name, sizeof(name), "%s-str", obj->name);
292 ret = -EAGAIN;
293 for (i = 0; ret && i < 2; i++) {
294 ret = fdt_property_string(abuf_data(buf), name, str);
295 if (!i) {
296 ret = check_space(ret, buf);
297 if (ret)
298 return log_msg_ret("rs2", -ENOMEM);
299 }
300 }
301
302 /* this should not happen */
303 if (ret)
304 return log_msg_ret("wr2", -EFAULT);
305
306 break;
307 }
308 }
309
310 return 0;
311}
312
313int cedit_write_settings(struct expo *exp, struct abuf *buf)
314{
315 struct cedit_iter_priv priv;
316 void *fdt;
317 int ret;
318
319 abuf_init(buf);
320 if (!abuf_realloc(buf, CEDIT_SIZE_INC))
321 return log_msg_ret("buf", -ENOMEM);
322
323 fdt = abuf_data(buf);
324 ret = fdt_create(fdt, abuf_size(buf));
325 if (!ret)
326 ret = fdt_finish_reservemap(fdt);
327 if (!ret)
328 ret = fdt_begin_node(fdt, "");
329 if (!ret)
330 ret = fdt_begin_node(fdt, CEDIT_NODE_NAME);
331 if (ret) {
332 log_debug("Failed to start FDT (err=%d)\n", ret);
333 return log_msg_ret("sta", -EINVAL);
334 }
335
336 /* write out the items */
337 priv.buf = buf;
338 ret = expo_iter_scene_objs(exp, h_write_settings, &priv);
339 if (ret) {
340 log_debug("Failed to write settings (err=%d)\n", ret);
341 return log_msg_ret("set", ret);
342 }
343
344 ret = fdt_end_node(fdt);
345 if (!ret)
346 ret = fdt_end_node(fdt);
347 if (!ret)
348 ret = fdt_finish(fdt);
349 if (ret) {
350 log_debug("Failed to finish FDT (err=%d)\n", ret);
351 return log_msg_ret("fin", -EINVAL);
352 }
353
354 return 0;
355}
Simon Glassb1cd32b2023-08-14 16:40:34 -0600356
357static int h_read_settings(struct scene_obj *obj, void *vpriv)
358{
359 struct cedit_iter_priv *priv = vpriv;
360 ofnode node = priv->node;
361
362 switch (obj->type) {
363 case SCENEOBJT_NONE:
364 case SCENEOBJT_IMAGE:
365 case SCENEOBJT_TEXT:
366 break;
367 case SCENEOBJT_MENU: {
368 struct scene_obj_menu *menu;
369 uint val;
370
371 if (ofnode_read_u32(node, obj->name, &val))
372 return log_msg_ret("rd", -ENOENT);
373 menu = (struct scene_obj_menu *)obj;
374 menu->cur_item_id = val;
375
376 break;
377 }
378 }
379
380 return 0;
381}
382
383int cedit_read_settings(struct expo *exp, oftree tree)
384{
385 struct cedit_iter_priv priv;
386 ofnode root, node;
387 int ret;
388
389 root = oftree_root(tree);
390 if (!ofnode_valid(root))
391 return log_msg_ret("roo", -ENOENT);
392 node = ofnode_find_subnode(root, CEDIT_NODE_NAME);
393 if (!ofnode_valid(node))
394 return log_msg_ret("pat", -ENOENT);
395
396 /* read in the items */
397 priv.node = node;
398 ret = expo_iter_scene_objs(exp, h_read_settings, &priv);
399 if (ret) {
400 log_debug("Failed to read settings (err=%d)\n", ret);
401 return log_msg_ret("set", ret);
402 }
403
404 return 0;
405}
Simon Glass237f3752023-08-14 16:40:35 -0600406
407static int h_write_settings_env(struct scene_obj *obj, void *vpriv)
408{
409 const struct scene_obj_menu *menu;
410 struct cedit_iter_priv *priv = vpriv;
411 char name[80], var[60];
412 const char *str;
413 int val, ret;
414
415 if (obj->type != SCENEOBJT_MENU)
416 return 0;
417
418 menu = (struct scene_obj_menu *)obj;
419 val = menu->cur_item_id;
420 snprintf(var, sizeof(var), "c.%s", obj->name);
421
422 if (priv->verbose)
423 printf("%s=%d\n", var, val);
424
425 ret = env_set_ulong(var, val);
426 if (ret)
427 return log_msg_ret("set", ret);
428
429 ret = get_cur_menuitem_text(menu, &str);
430 if (ret)
431 return log_msg_ret("mis", ret);
432
433 snprintf(name, sizeof(name), "c.%s-str", obj->name);
434 if (priv->verbose)
435 printf("%s=%s\n", name, str);
436
437 ret = env_set(name, str);
438 if (ret)
439 return log_msg_ret("st2", ret);
440
441 return 0;
442}
443
444int cedit_write_settings_env(struct expo *exp, bool verbose)
445{
446 struct cedit_iter_priv priv;
447 int ret;
448
449 /* write out the items */
450 priv.verbose = verbose;
451 ret = expo_iter_scene_objs(exp, h_write_settings_env, &priv);
452 if (ret) {
453 log_debug("Failed to write settings to env (err=%d)\n", ret);
454 return log_msg_ret("set", ret);
455 }
456
457 return 0;
458}
Simon Glass0f2e5a62023-08-14 16:40:36 -0600459
460static int h_read_settings_env(struct scene_obj *obj, void *vpriv)
461{
462 struct cedit_iter_priv *priv = vpriv;
463 struct scene_obj_menu *menu;
464 char var[60];
Simon Glass2b91ca62023-08-14 16:40:37 -0600465 int val;
Simon Glass0f2e5a62023-08-14 16:40:36 -0600466
467 if (obj->type != SCENEOBJT_MENU)
468 return 0;
469
470 menu = (struct scene_obj_menu *)obj;
471 val = menu->cur_item_id;
472 snprintf(var, sizeof(var), "c.%s", obj->name);
473
474 val = env_get_ulong(var, 10, 0);
475 if (priv->verbose)
476 printf("%s=%d\n", var, val);
477 if (!val)
478 return log_msg_ret("get", -ENOENT);
479
480 /*
481 * note that no validation is done here, to make sure the ID is valid
482 * and actually points to a menu item
483 */
484 menu->cur_item_id = val;
485
486 return 0;
487}
488
489int cedit_read_settings_env(struct expo *exp, bool verbose)
490{
491 struct cedit_iter_priv priv;
492 int ret;
493
494 /* write out the items */
495 priv.verbose = verbose;
496 ret = expo_iter_scene_objs(exp, h_read_settings_env, &priv);
497 if (ret) {
498 log_debug("Failed to read settings from env (err=%d)\n", ret);
499 return log_msg_ret("set", ret);
500 }
501
502 return 0;
503}
Simon Glass2b91ca62023-08-14 16:40:37 -0600504
505/**
506 * get_cur_menuitem_seq() - Get the sequence number of a menu's current item
507 *
508 * Enumerates the items of a menu (0, 1, 2) and returns the sequence number of
509 * the currently selected item. If the first item is selected, this returns 0;
510 * if the second, 1; etc.
511 *
512 * @menu: Menu to check
513 * Return: Sequence number on success, else -ve error value
514 */
515static int get_cur_menuitem_seq(const struct scene_obj_menu *menu)
516{
517 const struct scene_menitem *mi;
518 int seq, found;
519
520 seq = 0;
521 found = -1;
522 list_for_each_entry(mi, &menu->item_head, sibling) {
523 if (mi->id == menu->cur_item_id) {
524 found = seq;
525 break;
526 }
527 seq++;
528 }
529
530 if (found == -1)
531 return log_msg_ret("nf", -ENOENT);
532
533 return found;
534}
535
536static int h_write_settings_cmos(struct scene_obj *obj, void *vpriv)
537{
538 const struct scene_obj_menu *menu;
539 struct cedit_iter_priv *priv = vpriv;
540 int val, ret;
541 uint i, seq;
542
543 if (obj->type != SCENEOBJT_MENU)
544 return 0;
545
546 menu = (struct scene_obj_menu *)obj;
547 val = menu->cur_item_id;
548
549 ret = get_cur_menuitem_seq(menu);
550 if (ret < 0)
551 return log_msg_ret("cur", ret);
552 seq = ret;
553 log_debug("%s: seq=%d\n", menu->obj.name, seq);
554
555 /* figure out where to place this item */
556 if (!obj->bit_length)
557 return log_msg_ret("len", -EINVAL);
558 if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
559 return log_msg_ret("bit", -E2BIG);
560
561 for (i = 0; i < obj->bit_length; i++, seq >>= 1) {
562 uint bitnum = obj->start_bit + i;
563
564 priv->mask[CMOS_BYTE(bitnum)] |= 1 << CMOS_BIT(bitnum);
565 if (seq & 1)
566 priv->value[CMOS_BYTE(bitnum)] |= BIT(CMOS_BIT(bitnum));
567 log_debug("bit %x %x %x\n", bitnum,
568 priv->mask[CMOS_BYTE(bitnum)],
569 priv->value[CMOS_BYTE(bitnum)]);
570 }
571
572 return 0;
573}
574
575int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev,
576 bool verbose)
577{
578 struct cedit_iter_priv priv;
579 int ret, i, count, first, last;
580
581 /* write out the items */
582 priv.mask = calloc(1, CMOS_MAX_BYTES);
583 if (!priv.mask)
584 return log_msg_ret("mas", -ENOMEM);
585 priv.value = calloc(1, CMOS_MAX_BYTES);
586 if (!priv.value) {
587 free(priv.mask);
588 return log_msg_ret("val", -ENOMEM);
589 }
590
591 ret = expo_iter_scene_objs(exp, h_write_settings_cmos, &priv);
592 if (ret) {
593 log_debug("Failed to write CMOS (err=%d)\n", ret);
594 ret = log_msg_ret("set", ret);
595 goto done;
596 }
597
598 /* write the data to the RTC */
599 first = CMOS_MAX_BYTES;
600 last = -1;
601 for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
602 if (priv.mask[i]) {
603 log_debug("Write byte %x: %x\n", i, priv.value[i]);
604 ret = rtc_write8(dev, i, priv.value[i]);
605 if (ret) {
606 ret = log_msg_ret("wri", ret);
607 goto done;
608 }
609 count++;
610 first = min(first, i);
611 last = max(last, i);
612 }
613 }
614 if (verbose) {
615 printf("Write %d bytes from offset %x to %x\n", count, first,
616 last);
617 }
618
619done:
620 free(priv.mask);
621 free(priv.value);
622 return ret;
623}
Simon Glass4462fa32023-08-14 16:40:38 -0600624
625static int h_read_settings_cmos(struct scene_obj *obj, void *vpriv)
626{
627 struct cedit_iter_priv *priv = vpriv;
628 const struct scene_menitem *mi;
629 struct scene_obj_menu *menu;
630 int val, ret;
631 uint i;
632
633 if (obj->type != SCENEOBJT_MENU)
634 return 0;
635
636 menu = (struct scene_obj_menu *)obj;
637
638 /* figure out where to place this item */
639 if (!obj->bit_length)
640 return log_msg_ret("len", -EINVAL);
641 if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
642 return log_msg_ret("bit", -E2BIG);
643
644 val = 0;
645 for (i = 0; i < obj->bit_length; i++) {
646 uint bitnum = obj->start_bit + i;
647 uint offset = CMOS_BYTE(bitnum);
648
649 /* read the byte if not already read */
650 if (!priv->mask[offset]) {
651 ret = rtc_read8(priv->dev, offset);
652 if (ret < 0)
653 return log_msg_ret("rea", ret);
654 priv->value[offset] = ret;
655
656 /* mark it as read */
657 priv->mask[offset] = 0xff;
658 }
659
660 if (priv->value[offset] & BIT(CMOS_BIT(bitnum)))
661 val |= BIT(i);
662 log_debug("bit %x %x\n", bitnum, val);
663 }
664
665 /* update the current item */
666 mi = scene_menuitem_find_seq(menu, val);
667 if (!mi)
668 return log_msg_ret("seq", -ENOENT);
669
670 menu->cur_item_id = mi->id;
671 log_debug("Update menu %d cur_item_id %d\n", menu->obj.id, mi->id);
672
673 return 0;
674}
675
676int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
677 bool verbose)
678{
679 struct cedit_iter_priv priv;
680 int ret, i, count, first, last;
681
682 /* read in the items */
683 priv.mask = calloc(1, CMOS_MAX_BYTES);
684 if (!priv.mask)
685 return log_msg_ret("mas", -ENOMEM);
686 priv.value = calloc(1, CMOS_MAX_BYTES);
687 if (!priv.value) {
688 free(priv.mask);
689 return log_msg_ret("val", -ENOMEM);
690 }
691 priv.dev = dev;
692
693 ret = expo_iter_scene_objs(exp, h_read_settings_cmos, &priv);
694 if (ret) {
695 log_debug("Failed to read CMOS (err=%d)\n", ret);
696 ret = log_msg_ret("set", ret);
697 goto done;
698 }
699
700 /* read the data to the RTC */
701 first = CMOS_MAX_BYTES;
702 last = -1;
703 for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
704 if (priv.mask[i]) {
705 log_debug("Read byte %x: %x\n", i, priv.value[i]);
706 count++;
707 first = min(first, i);
708 last = max(last, i);
709 }
710 }
711 if (verbose) {
712 printf("Read %d bytes from offset %x to %x\n", count, first,
713 last);
714 }
715
716done:
717 free(priv.mask);
718 free(priv.value);
719 return ret;
720}