1// 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 9#define LOG_CATEGORY LOGC_EXPO 10 11#include <common.h> 12#include <abuf.h> 13#include <cedit.h> 14#include <cli.h> 15#include <dm.h> 16#include <env.h> 17#include <expo.h> 18#include <malloc.h> 19#include <menu.h> 20#include <rtc.h> 21#include <video.h> 22#include <linux/delay.h> 23#include "scene_internal.h" 24 25enum { 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 33/** 34 * struct cedit_iter_priv - private data for cedit operations 35 * 36 * @buf: Buffer to use when writing settings to the devicetree 37 * @node: Node to read from when reading settings from devicetree 38 * @verbose: true to show writing to environment variables 39 * @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 42 * @dev: RTC device to write to 43 */ 44struct cedit_iter_priv { 45 struct abuf *buf; 46 ofnode node; 47 bool verbose; 48 u8 *mask; 49 u8 *value; 50 struct udevice *dev; 51}; 52 53int 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 switch (obj->type) { 75 case SCENEOBJT_NONE: 76 case SCENEOBJT_IMAGE: 77 case SCENEOBJT_TEXT: 78 break; 79 case SCENEOBJT_MENU: 80 scene_obj_set_pos(scn, obj->id, 50, y); 81 scene_menu_arrange(scn, (struct scene_obj_menu *)obj); 82 y += 50; 83 break; 84 case SCENEOBJT_TEXTLINE: 85 scene_obj_set_pos(scn, obj->id, 50, y); 86 scene_textline_arrange(scn, 87 (struct scene_obj_textline *)obj); 88 y += 50; 89 break; 90 } 91 } 92 93 return 0; 94} 95 96int cedit_prepare(struct expo *exp, struct video_priv **vid_privp, 97 struct scene **scnp) 98{ 99 struct video_priv *vid_priv; 100 struct udevice *dev; 101 struct scene *scn; 102 uint scene_id; 103 int ret; 104 105 /* For now we only support a video console */ 106 ret = uclass_first_device_err(UCLASS_VIDEO, &dev); 107 if (ret) 108 return log_msg_ret("vid", ret); 109 ret = expo_set_display(exp, dev); 110 if (ret) 111 return log_msg_ret("dis", ret); 112 113 ret = expo_first_scene_id(exp); 114 if (ret < 0) 115 return log_msg_ret("scn", ret); 116 scene_id = ret; 117 118 ret = expo_set_scene_id(exp, scene_id); 119 if (ret) 120 return log_msg_ret("sid", ret); 121 122 exp->popup = true; 123 124 /* This is not supported for now */ 125 if (0) 126 expo_set_text_mode(exp, true); 127 128 vid_priv = dev_get_uclass_priv(dev); 129 130 scn = expo_lookup_scene_id(exp, scene_id); 131 scene_highlight_first(scn); 132 133 cedit_arange(exp, vid_priv, scene_id); 134 135 ret = expo_calc_dims(exp); 136 if (ret) 137 return log_msg_ret("dim", ret); 138 139 *vid_privp = vid_priv; 140 *scnp = scn; 141 142 return scene_id; 143} 144 145int cedit_run(struct expo *exp) 146{ 147 struct cli_ch_state s_cch, *cch = &s_cch; 148 struct video_priv *vid_priv; 149 uint scene_id; 150 struct scene *scn; 151 bool done; 152 int ret; 153 154 cli_ch_init(cch); 155 ret = cedit_prepare(exp, &vid_priv, &scn); 156 if (ret < 0) 157 return log_msg_ret("prep", ret); 158 scene_id = ret; 159 160 done = false; 161 do { 162 struct expo_action act; 163 int ichar, key; 164 165 ret = expo_render(exp); 166 if (ret) 167 break; 168 169 ichar = cli_ch_process(cch, 0); 170 if (!ichar) { 171 while (!ichar && !tstc()) { 172 schedule(); 173 mdelay(2); 174 ichar = cli_ch_process(cch, -ETIMEDOUT); 175 } 176 if (!ichar) { 177 ichar = getchar(); 178 ichar = cli_ch_process(cch, ichar); 179 } 180 } 181 182 key = 0; 183 if (ichar) { 184 key = bootmenu_conv_key(ichar); 185 if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA) 186 key = ichar; 187 } 188 if (!key) 189 continue; 190 191 ret = expo_send_key(exp, key); 192 if (ret) 193 break; 194 195 ret = expo_action_get(exp, &act); 196 if (!ret) { 197 switch (act.type) { 198 case EXPOACT_POINT_OBJ: 199 scene_set_highlight_id(scn, act.select.id); 200 cedit_arange(exp, vid_priv, scene_id); 201 break; 202 case EXPOACT_OPEN: 203 scene_set_open(scn, act.select.id, true); 204 cedit_arange(exp, vid_priv, scene_id); 205 break; 206 case EXPOACT_CLOSE: 207 scene_set_open(scn, act.select.id, false); 208 cedit_arange(exp, vid_priv, scene_id); 209 break; 210 case EXPOACT_SELECT: 211 scene_set_open(scn, scn->highlight_id, false); 212 cedit_arange(exp, vid_priv, scene_id); 213 break; 214 case EXPOACT_QUIT: 215 log_debug("quitting\n"); 216 done = true; 217 break; 218 default: 219 break; 220 } 221 } 222 } while (!done); 223 224 if (ret) 225 return log_msg_ret("end", ret); 226 227 return 0; 228} 229 230static int check_space(int ret, struct abuf *buf) 231{ 232 if (ret == -FDT_ERR_NOSPACE) { 233 if (!abuf_realloc_inc(buf, CEDIT_SIZE_INC)) 234 return log_msg_ret("spc", -ENOMEM); 235 ret = fdt_resize(abuf_data(buf), abuf_data(buf), 236 abuf_size(buf)); 237 if (ret) 238 return log_msg_ret("res", -EFAULT); 239 } 240 241 return 0; 242} 243 244/** 245 * get_cur_menuitem_text() - Get the text of the currently selected item 246 * 247 * Looks up the object for the current item, finds text object for it and looks 248 * up the string for that text 249 * 250 * @menu: Menu to look at 251 * @strp: Returns a pointer to the next 252 * Return: 0 if OK, -ENOENT if something was not found 253 */ 254static int get_cur_menuitem_text(const struct scene_obj_menu *menu, 255 const char **strp) 256{ 257 struct scene *scn = menu->obj.scene; 258 const struct scene_menitem *mi; 259 const struct scene_obj_txt *txt; 260 const char *str; 261 262 mi = scene_menuitem_find(menu, menu->cur_item_id); 263 if (!mi) 264 return log_msg_ret("mi", -ENOENT); 265 266 txt = scene_obj_find(scn, mi->label_id, SCENEOBJT_TEXT); 267 if (!txt) 268 return log_msg_ret("txt", -ENOENT); 269 270 str = expo_get_str(scn->expo, txt->str_id); 271 if (!str) 272 return log_msg_ret("str", -ENOENT); 273 *strp = str; 274 275 return 0; 276} 277 278static int write_dt_string(struct abuf *buf, const char *name, const char *str) 279{ 280 int ret, i; 281 282 /* write the text of the current item */ 283 ret = -EAGAIN; 284 for (i = 0; ret && i < 2; i++) { 285 ret = fdt_property_string(abuf_data(buf), name, str); 286 if (!i) { 287 ret = check_space(ret, buf); 288 if (ret) 289 return log_msg_ret("rs2", -ENOMEM); 290 } 291 } 292 293 /* this should not happen */ 294 if (ret) 295 return log_msg_ret("str", -EFAULT); 296 297 return 0; 298} 299 300static int h_write_settings(struct scene_obj *obj, void *vpriv) 301{ 302 struct cedit_iter_priv *priv = vpriv; 303 struct abuf *buf = priv->buf; 304 int ret; 305 306 switch (obj->type) { 307 case SCENEOBJT_NONE: 308 case SCENEOBJT_IMAGE: 309 case SCENEOBJT_TEXT: 310 break; 311 case SCENEOBJT_TEXTLINE: { 312 const struct scene_obj_textline *tline; 313 314 tline = (struct scene_obj_textline *)obj; 315 ret = write_dt_string(buf, obj->name, abuf_data(&tline->buf)); 316 if (ret) 317 return log_msg_ret("wr2", ret); 318 break; 319 } 320 case SCENEOBJT_MENU: { 321 const struct scene_obj_menu *menu; 322 const char *str; 323 char name[80]; 324 int i; 325 326 /* write the ID of the current item */ 327 menu = (struct scene_obj_menu *)obj; 328 ret = -EAGAIN; 329 for (i = 0; ret && i < 2; i++) { 330 ret = fdt_property_u32(abuf_data(buf), obj->name, 331 menu->cur_item_id); 332 if (!i) { 333 ret = check_space(ret, buf); 334 if (ret) 335 return log_msg_ret("res", -ENOMEM); 336 } 337 } 338 /* this should not happen */ 339 if (ret) 340 return log_msg_ret("wrt", -EFAULT); 341 342 ret = get_cur_menuitem_text(menu, &str); 343 if (ret) 344 return log_msg_ret("mis", ret); 345 346 /* write the text of the current item */ 347 snprintf(name, sizeof(name), "%s-str", obj->name); 348 ret = write_dt_string(buf, name, str); 349 if (ret) 350 return log_msg_ret("wr2", ret); 351 352 break; 353 } 354 } 355 356 return 0; 357} 358 359int cedit_write_settings(struct expo *exp, struct abuf *buf) 360{ 361 struct cedit_iter_priv priv; 362 void *fdt; 363 int ret; 364 365 abuf_init(buf); 366 if (!abuf_realloc(buf, CEDIT_SIZE_INC)) 367 return log_msg_ret("buf", -ENOMEM); 368 369 fdt = abuf_data(buf); 370 ret = fdt_create(fdt, abuf_size(buf)); 371 if (!ret) 372 ret = fdt_finish_reservemap(fdt); 373 if (!ret) 374 ret = fdt_begin_node(fdt, ""); 375 if (!ret) 376 ret = fdt_begin_node(fdt, CEDIT_NODE_NAME); 377 if (ret) { 378 log_debug("Failed to start FDT (err=%d)\n", ret); 379 return log_msg_ret("sta", -EINVAL); 380 } 381 382 /* write out the items */ 383 priv.buf = buf; 384 ret = expo_iter_scene_objs(exp, h_write_settings, &priv); 385 if (ret) { 386 log_debug("Failed to write settings (err=%d)\n", ret); 387 return log_msg_ret("set", ret); 388 } 389 390 ret = fdt_end_node(fdt); 391 if (!ret) 392 ret = fdt_end_node(fdt); 393 if (!ret) 394 ret = fdt_finish(fdt); 395 if (ret) { 396 log_debug("Failed to finish FDT (err=%d)\n", ret); 397 return log_msg_ret("fin", -EINVAL); 398 } 399 400 return 0; 401} 402 403static int h_read_settings(struct scene_obj *obj, void *vpriv) 404{ 405 struct cedit_iter_priv *priv = vpriv; 406 ofnode node = priv->node; 407 408 switch (obj->type) { 409 case SCENEOBJT_NONE: 410 case SCENEOBJT_IMAGE: 411 case SCENEOBJT_TEXT: 412 break; 413 case SCENEOBJT_TEXTLINE: { 414 const struct scene_obj_textline *tline; 415 const char *val; 416 int len; 417 418 tline = (struct scene_obj_textline *)obj; 419 420 val = ofnode_read_prop(node, obj->name, &len); 421 if (len >= tline->max_chars) 422 return log_msg_ret("str", -ENOSPC); 423 strcpy(abuf_data(&tline->buf), val); 424 break; 425 } 426 case SCENEOBJT_MENU: { 427 struct scene_obj_menu *menu; 428 uint val; 429 430 if (ofnode_read_u32(node, obj->name, &val)) 431 return log_msg_ret("rd", -ENOENT); 432 menu = (struct scene_obj_menu *)obj; 433 menu->cur_item_id = val; 434 435 break; 436 } 437 } 438 439 return 0; 440} 441 442int cedit_read_settings(struct expo *exp, oftree tree) 443{ 444 struct cedit_iter_priv priv; 445 ofnode root, node; 446 int ret; 447 448 root = oftree_root(tree); 449 if (!ofnode_valid(root)) 450 return log_msg_ret("roo", -ENOENT); 451 node = ofnode_find_subnode(root, CEDIT_NODE_NAME); 452 if (!ofnode_valid(node)) 453 return log_msg_ret("pat", -ENOENT); 454 455 /* read in the items */ 456 priv.node = node; 457 ret = expo_iter_scene_objs(exp, h_read_settings, &priv); 458 if (ret) { 459 log_debug("Failed to read settings (err=%d)\n", ret); 460 return log_msg_ret("set", ret); 461 } 462 463 return 0; 464} 465 466static int h_write_settings_env(struct scene_obj *obj, void *vpriv) 467{ 468 const struct scene_obj_menu *menu; 469 struct cedit_iter_priv *priv = vpriv; 470 char name[80], var[60]; 471 const char *str; 472 int val, ret; 473 474 snprintf(var, sizeof(var), "c.%s", obj->name); 475 476 switch (obj->type) { 477 case SCENEOBJT_NONE: 478 case SCENEOBJT_IMAGE: 479 case SCENEOBJT_TEXT: 480 break; 481 case SCENEOBJT_MENU: 482 menu = (struct scene_obj_menu *)obj; 483 val = menu->cur_item_id; 484 485 if (priv->verbose) 486 printf("%s=%d\n", var, val); 487 488 ret = env_set_ulong(var, val); 489 if (ret) 490 return log_msg_ret("set", ret); 491 492 ret = get_cur_menuitem_text(menu, &str); 493 if (ret) 494 return log_msg_ret("mis", ret); 495 496 snprintf(name, sizeof(name), "c.%s-str", obj->name); 497 if (priv->verbose) 498 printf("%s=%s\n", name, str); 499 500 ret = env_set(name, str); 501 if (ret) 502 return log_msg_ret("st2", ret); 503 break; 504 case SCENEOBJT_TEXTLINE: { 505 const struct scene_obj_textline *tline; 506 507 tline = (struct scene_obj_textline *)obj; 508 str = abuf_data(&tline->buf); 509 ret = env_set(var, str); 510 if (ret) 511 return log_msg_ret("set", ret); 512 513 if (priv->verbose) 514 printf("%s=%s\n", var, str); 515 516 break; 517 } 518 } 519 520 return 0; 521} 522 523int cedit_write_settings_env(struct expo *exp, bool verbose) 524{ 525 struct cedit_iter_priv priv; 526 int ret; 527 528 /* write out the items */ 529 priv.verbose = verbose; 530 ret = expo_iter_scene_objs(exp, h_write_settings_env, &priv); 531 if (ret) { 532 log_debug("Failed to write settings to env (err=%d)\n", ret); 533 return log_msg_ret("set", ret); 534 } 535 536 return 0; 537} 538 539static int h_read_settings_env(struct scene_obj *obj, void *vpriv) 540{ 541 struct cedit_iter_priv *priv = vpriv; 542 struct scene_obj_menu *menu; 543 char var[60]; 544 int val; 545 546 snprintf(var, sizeof(var), "c.%s", obj->name); 547 548 switch (obj->type) { 549 case SCENEOBJT_NONE: 550 case SCENEOBJT_IMAGE: 551 case SCENEOBJT_TEXT: 552 break; 553 case SCENEOBJT_MENU: 554 menu = (struct scene_obj_menu *)obj; 555 val = env_get_ulong(var, 10, 0); 556 if (priv->verbose) 557 printf("%s=%d\n", var, val); 558 if (!val) 559 return log_msg_ret("get", -ENOENT); 560 561 /* 562 * note that no validation is done here, to make sure the ID is 563 * valid * and actually points to a menu item 564 */ 565 menu->cur_item_id = val; 566 break; 567 case SCENEOBJT_TEXTLINE: { 568 const struct scene_obj_textline *tline; 569 const char *value; 570 571 tline = (struct scene_obj_textline *)obj; 572 value = env_get(var); 573 if (value && strlen(value) >= tline->max_chars) 574 return log_msg_ret("str", -ENOSPC); 575 if (!value) 576 value = ""; 577 if (priv->verbose) 578 printf("%s=%s\n", var, value); 579 strcpy(abuf_data(&tline->buf), value); 580 break; 581 } 582 } 583 584 return 0; 585} 586 587int cedit_read_settings_env(struct expo *exp, bool verbose) 588{ 589 struct cedit_iter_priv priv; 590 int ret; 591 592 /* write out the items */ 593 priv.verbose = verbose; 594 ret = expo_iter_scene_objs(exp, h_read_settings_env, &priv); 595 if (ret) { 596 log_debug("Failed to read settings from env (err=%d)\n", ret); 597 return log_msg_ret("set", ret); 598 } 599 600 return 0; 601} 602 603/** 604 * get_cur_menuitem_seq() - Get the sequence number of a menu's current item 605 * 606 * Enumerates the items of a menu (0, 1, 2) and returns the sequence number of 607 * the currently selected item. If the first item is selected, this returns 0; 608 * if the second, 1; etc. 609 * 610 * @menu: Menu to check 611 * Return: Sequence number on success, else -ve error value 612 */ 613static int get_cur_menuitem_seq(const struct scene_obj_menu *menu) 614{ 615 const struct scene_menitem *mi; 616 int seq, found; 617 618 seq = 0; 619 found = -1; 620 list_for_each_entry(mi, &menu->item_head, sibling) { 621 if (mi->id == menu->cur_item_id) { 622 found = seq; 623 break; 624 } 625 seq++; 626 } 627 628 if (found == -1) 629 return log_msg_ret("nf", -ENOENT); 630 631 return found; 632} 633 634static int h_write_settings_cmos(struct scene_obj *obj, void *vpriv) 635{ 636 const struct scene_obj_menu *menu; 637 struct cedit_iter_priv *priv = vpriv; 638 int val, ret; 639 uint i, seq; 640 641 if (obj->type != SCENEOBJT_MENU) 642 return 0; 643 644 menu = (struct scene_obj_menu *)obj; 645 val = menu->cur_item_id; 646 647 ret = get_cur_menuitem_seq(menu); 648 if (ret < 0) 649 return log_msg_ret("cur", ret); 650 seq = ret; 651 log_debug("%s: seq=%d\n", menu->obj.name, seq); 652 653 /* figure out where to place this item */ 654 if (!obj->bit_length) 655 return log_msg_ret("len", -EINVAL); 656 if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS) 657 return log_msg_ret("bit", -E2BIG); 658 659 for (i = 0; i < obj->bit_length; i++, seq >>= 1) { 660 uint bitnum = obj->start_bit + i; 661 662 priv->mask[CMOS_BYTE(bitnum)] |= 1 << CMOS_BIT(bitnum); 663 if (seq & 1) 664 priv->value[CMOS_BYTE(bitnum)] |= BIT(CMOS_BIT(bitnum)); 665 log_debug("bit %x %x %x\n", bitnum, 666 priv->mask[CMOS_BYTE(bitnum)], 667 priv->value[CMOS_BYTE(bitnum)]); 668 } 669 670 return 0; 671} 672 673int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev, 674 bool verbose) 675{ 676 struct cedit_iter_priv priv; 677 int ret, i, count, first, last; 678 679 /* write out the items */ 680 priv.mask = calloc(1, CMOS_MAX_BYTES); 681 if (!priv.mask) 682 return log_msg_ret("mas", -ENOMEM); 683 priv.value = calloc(1, CMOS_MAX_BYTES); 684 if (!priv.value) { 685 free(priv.mask); 686 return log_msg_ret("val", -ENOMEM); 687 } 688 689 ret = expo_iter_scene_objs(exp, h_write_settings_cmos, &priv); 690 if (ret) { 691 log_debug("Failed to write CMOS (err=%d)\n", ret); 692 ret = log_msg_ret("set", ret); 693 goto done; 694 } 695 696 /* write the data to the RTC */ 697 first = CMOS_MAX_BYTES; 698 last = -1; 699 for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) { 700 if (priv.mask[i]) { 701 log_debug("Write byte %x: %x\n", i, priv.value[i]); 702 ret = rtc_write8(dev, i, priv.value[i]); 703 if (ret) { 704 ret = log_msg_ret("wri", ret); 705 goto done; 706 } 707 count++; 708 first = min(first, i); 709 last = max(last, i); 710 } 711 } 712 if (verbose) { 713 printf("Write %d bytes from offset %x to %x\n", count, first, 714 last); 715 } 716 717done: 718 free(priv.mask); 719 free(priv.value); 720 return ret; 721} 722 723static int h_read_settings_cmos(struct scene_obj *obj, void *vpriv) 724{ 725 struct cedit_iter_priv *priv = vpriv; 726 const struct scene_menitem *mi; 727 struct scene_obj_menu *menu; 728 int val, ret; 729 uint i; 730 731 if (obj->type != SCENEOBJT_MENU) 732 return 0; 733 734 menu = (struct scene_obj_menu *)obj; 735 736 /* figure out where to place this item */ 737 if (!obj->bit_length) 738 return log_msg_ret("len", -EINVAL); 739 if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS) 740 return log_msg_ret("bit", -E2BIG); 741 742 val = 0; 743 for (i = 0; i < obj->bit_length; i++) { 744 uint bitnum = obj->start_bit + i; 745 uint offset = CMOS_BYTE(bitnum); 746 747 /* read the byte if not already read */ 748 if (!priv->mask[offset]) { 749 ret = rtc_read8(priv->dev, offset); 750 if (ret < 0) 751 return log_msg_ret("rea", ret); 752 priv->value[offset] = ret; 753 754 /* mark it as read */ 755 priv->mask[offset] = 0xff; 756 } 757 758 if (priv->value[offset] & BIT(CMOS_BIT(bitnum))) 759 val |= BIT(i); 760 log_debug("bit %x %x\n", bitnum, val); 761 } 762 763 /* update the current item */ 764 mi = scene_menuitem_find_seq(menu, val); 765 if (!mi) 766 return log_msg_ret("seq", -ENOENT); 767 768 menu->cur_item_id = mi->id; 769 log_debug("Update menu %d cur_item_id %d\n", menu->obj.id, mi->id); 770 771 return 0; 772} 773 774int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev, 775 bool verbose) 776{ 777 struct cedit_iter_priv priv; 778 int ret, i, count, first, last; 779 780 /* read in the items */ 781 priv.mask = calloc(1, CMOS_MAX_BYTES); 782 if (!priv.mask) 783 return log_msg_ret("mas", -ENOMEM); 784 priv.value = calloc(1, CMOS_MAX_BYTES); 785 if (!priv.value) { 786 free(priv.mask); 787 return log_msg_ret("val", -ENOMEM); 788 } 789 priv.dev = dev; 790 791 ret = expo_iter_scene_objs(exp, h_read_settings_cmos, &priv); 792 if (ret) { 793 log_debug("Failed to read CMOS (err=%d)\n", ret); 794 ret = log_msg_ret("set", ret); 795 goto done; 796 } 797 798 /* read the data to the RTC */ 799 first = CMOS_MAX_BYTES; 800 last = -1; 801 for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) { 802 if (priv.mask[i]) { 803 log_debug("Read byte %x: %x\n", i, priv.value[i]); 804 count++; 805 first = min(first, i); 806 last = max(last, i); 807 } 808 } 809 if (verbose) { 810 printf("Read %d bytes from offset %x to %x\n", count, first, 811 last); 812 } 813 814done: 815 free(priv.mask); 816 free(priv.value); 817 return ret; 818} 819