/* * textbox.c -- implements the text box * * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "dialog.h" static void back_lines(int n); static void print_page(WINDOW *win, int height, int width, update_text_fn update_text, void *data); static void print_line(WINDOW *win, int row, int width); static char *get_line(void); static void print_position(WINDOW * win); static int hscroll; static int begin_reached, end_reached, page_length; static char *buf; static char *page; /* * refresh window content */ static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, int cur_y, int cur_x, update_text_fn update_text, void *data) { print_page(box, boxh, boxw, update_text, data); print_position(dialog); wmove(dialog, cur_y, cur_x); /* Restore cursor position */ wrefresh(dialog); } /* * Display text from a file in a dialog box. * * keys is a null-terminated array * update_text() may not add or remove any '\n' or '\0' in tbuf */ int dialog_textbox(const char *title, char *tbuf, int initial_height, int initial_width, int *keys, int *_vscroll, int *_hscroll, update_text_fn update_text, void *data) { int i, x, y, cur_x, cur_y, key = 0; int height, width, boxh, boxw; WINDOW *dialog, *box; bool done = false; begin_reached = 1; end_reached = 0; page_length = 0; hscroll = 0; buf = tbuf; page = buf; /* page is pointer to start of page to be displayed */ if (_vscroll && *_vscroll) { begin_reached = 0; for (i = 0; i < *_vscroll; i++) get_line(); } if (_hscroll) hscroll = *_hscroll; do_resize: getmaxyx(stdscr, height, width); if (height < TEXTBOX_HEIGTH_MIN || width < TEXTBOX_WIDTH_MIN) return -ERRDISPLAYTOOSMALL; if (initial_height != 0) height = initial_height; else if (height > 4) height -= 4; else height = 0; if (initial_width != 0) width = initial_width; else if (width > 5) width -= 5; else width = 0; /* center dialog box on screen */ x = (getmaxx(stdscr) - width) / 2; y = (getmaxy(stdscr) - height) / 2; draw_shadow(stdscr, y, x, height, width); dialog = newwin(height, width, y, x); keypad(dialog, TRUE); /* Create window for box region, used for scrolling text */ boxh = height - 4; boxw = width - 2; box = subwin(dialog, boxh, boxw, y + 1, x + 1); wattrset(box, dlg.dialog.atr); wbkgdset(box, dlg.dialog.atr & A_COLOR); keypad(box, TRUE); /* register the new window, along with its borders */ draw_box(dialog, 0, 0, height, width, dlg.dialog.atr, dlg.border.atr); wattrset(dialog, dlg.border.atr); mvwaddch(dialog, height - 3, 0, ACS_LTEE); for (i = 0; i < width - 2; i++) waddch(dialog, ACS_HLINE); wattrset(dialog, dlg.dialog.atr); wbkgdset(dialog, dlg.dialog.atr & A_COLOR); waddch(dialog, ACS_RTEE); print_title(dialog, title, width); print_button(dialog, gettext(" Exit "), height - 2, width / 2 - 4, TRUE); wnoutrefresh(dialog); getyx(dialog, cur_y, cur_x); /* Save cursor position */ /* Print first page of text */ attr_clear(box, boxh, boxw, dlg.dialog.atr); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); while (!done) { key = wgetch(dialog); switch (key) { case 'E': /* Exit */ case 'e': case 'X': case 'x': case 'q': case '\n': done = true; break; case 'g': /* First page */ case KEY_HOME: if (!begin_reached) { begin_reached = 1; page = buf; refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); } break; case 'G': /* Last page */ case KEY_END: end_reached = 1; /* point to last char in buf */ page = buf + strlen(buf); back_lines(boxh); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case 'K': /* Previous line */ case 'k': case KEY_UP: if (begin_reached) break; back_lines(page_length + 1); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case 'B': /* Previous page */ case 'b': case 'u': case KEY_PPAGE: if (begin_reached) break; back_lines(page_length + boxh); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case 'J': /* Next line */ case 'j': case KEY_DOWN: if (end_reached) break; back_lines(page_length - 1); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case KEY_NPAGE: /* Next page */ case ' ': case 'd': if (end_reached) break; begin_reached = 0; refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case '0': /* Beginning of line */ case 'H': /* Scroll left */ case 'h': case KEY_LEFT: if (hscroll <= 0) break; if (key == '0') hscroll = 0; else hscroll--; /* Reprint current page to scroll horizontally */ back_lines(page_length); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case 'L': /* Scroll right */ case 'l': case KEY_RIGHT: if (hscroll >= MAX_LEN) break; hscroll++; /* Reprint current page to scroll horizontally */ back_lines(page_length); refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, data); break; case KEY_ESC: if (on_key_esc(dialog) == KEY_ESC) done = true; break; case KEY_RESIZE: back_lines(height); delwin(box); delwin(dialog); on_key_resize(); goto do_resize; default: for (i = 0; keys[i]; i++) { if (key == keys[i]) { done = true; break; } } } } delwin(box); delwin(dialog); if (_vscroll) { const char *s; s = buf; *_vscroll = 0; back_lines(page_length); while (s < page && (s = strchr(s, '\n'))) { (*_vscroll)++; s++; } } if (_hscroll) *_hscroll = hscroll; return key; } /* * Go back 'n' lines in text. Called by dialog_textbox(). * 'page' will be updated to point to the desired line in 'buf'. */ static void back_lines(int n) { int i; begin_reached = 0; /* Go back 'n' lines */ for (i = 0; i < n; i++) { if (*page == '\0') { if (end_reached) { end_reached = 0; continue; } } if (page == buf) { begin_reached = 1; return; } page--; do { if (page == buf) { begin_reached = 1; return; } page--; } while (*page != '\n'); page++; } } /* * Print a new page of text. */ static void print_page(WINDOW *win, int height, int width, update_text_fn update_text, void *data) { int i, passed_end = 0; if (update_text) { char *end; for (i = 0; i < height; i++) get_line(); end = page; back_lines(height); update_text(buf, page - buf, end - buf, data); } page_length = 0; for (i = 0; i < height; i++) { print_line(win, i, width); if (!passed_end) page_length++; if (end_reached && !passed_end) passed_end = 1; } wnoutrefresh(win); } /* * Print a new line of text. */ static void print_line(WINDOW * win, int row, int width) { char *line; line = get_line(); line += MIN(strlen(line), hscroll); /* Scroll horizontally */ wmove(win, row, 0); /* move cursor to correct line */ waddch(win, ' '); waddnstr(win, line, MIN(strlen(line), width - 2)); /* Clear 'residue' of previous line */ #if OLD_NCURSES { int x = getcurx(win); int i; for (i = 0; i < width - x; i++) waddch(win, ' '); } #else wclrtoeol(win); #endif } /* * Return current line of text. Called by dialog_textbox() and print_line(). * 'page' should point to start of current line before calling, and will be * updated to point to start of next line. */ static char *get_line(void) { int i = 0; static char line[MAX_LEN + 1]; end_reached = 0; while (*page != '\n') { if (*page == '\0') { end_reached = 1; break; } else if (i < MAX_LEN) line[i++] = *(page++); else { /* Truncate lines longer than MAX_LEN characters */ if (i == MAX_LEN) line[i++] = '\0'; page++; } } if (i <= MAX_LEN) line[i] = '\0'; if (!end_reached) page++; /* move past '\n' */ return line; } /* * Print current position */ static void print_position(WINDOW * win) { int percent; wattrset(win, dlg.position_indicator.atr); wbkgdset(win, dlg.position_indicator.atr & A_COLOR); percent = (page - buf) * 100 / strlen(buf); wmove(win, getmaxy(win) - 3, getmaxx(win) - 9); wprintw(win, "(%3d%%)", percent); } t+0x148/0x188) [ 240.705149] [<c0477d40>] (schedule_timeout) from [<c0476040>] (wait_for_common+0xa4/0x170) [ 240.713446] [<c0476040>] (wait_for_common) from [<c01f3300>] (submit_bio_wait+0x58/0x64) [ 240.721571] [<c01f3300>] (submit_bio_wait) from [<c01fbbd8>] (blkdev_issue_flush+0x60/0x88) [ 240.729957] [<c01fbbd8>] (blkdev_issue_flush) from [<c010ff84>] (blkdev_fsync+0x34/0x44) [ 240.738083] [<c010ff84>] (blkdev_fsync) from [<c0109594>] (do_fsync+0x3c/0x64) [ 240.745319] [<c0109594>] (do_fsync) from [<c000ffc0>] (ret_fast_syscall+0x0/0x3c) .. Here is the detailed sequence showing when this issue may happen: 1) At probe time, mmci device is initialized and card busy detection based on DAT[0] monitoring is enabled. 2) Later during run time, since card reported to support internal caches, a MMCI_SWITCH command is sent to eMMC device with FLUSH_CACHE operation. On receiving this command, eMMC may enter busy state (for a relatively short time in the case of the dead-lock). 3) Then mmci interrupt is raised and mmci_irq() is called: MMCISTATUS register is read and is equal to 0x01000440. So the following status bits are set: - MCI_CMDRESPEND (= 6) - MCI_DATABLOCKEND (= 10) - MCI_ST_CARDBUSY (= 24) Since MMCIMASK0 register is 0x3FF, status variable is set to 0x00000040 and BIT MCI_CMDRESPEND is cleared by writing MMCICLEAR register. Then mmci_cmd_irq() is called. Considering the following conditions: - host->busy_status is 0, - this is a "busy response", - reading again MMCISTATUS register gives 0x1000400, MMCIMASK0 is updated to unmask MCI_ST_BUSYEND bit. Thus, MMCIMASK0 is set to 0x010003FF and host->busy_status is set to wait for busy end completion. Back again in status loop of mmci_irq(), we quickly go through mmci_data_irq() as there are no data in that case. And we finally go through following test at the end of while(status) loop: /* * Don't poll for busy completion in irq context. */ if (host->variant->busy_detect && host->busy_status) status &= ~host->variant->busy_detect_flag; Because status variable is not yet null (is equal to 0x40), we do not leave interrupt context yet but we loop again into while(status) loop. So we run across following steps: a) MMCISTATUS register is read again and this time is equal to 0x01000400. So that following bits are set: - MCI_DATABLOCKEND (= 10) - MCI_ST_CARDBUSY (= 24) Since MMCIMASK0 register is equal to 0x010003FF: b) status variable is set to 0x01000000. c) MCI_ST_CARDBUSY bit is cleared by writing MMCICLEAR register. Then, mmci_cmd_irq() is called one more time. Since host->busy_status is set and that MCI_ST_CARDBUSY is set in status variable, we just return from this function. Back again in mmci_irq(), status variable is set to 0 and we finally leave the while(status) loop. As a result we leave interrupt context, waiting for busy end interrupt event. Now, consider that busy end completion is raised IN BETWEEN steps 3.a) and 3.c). In such a case, we may mistakenly clear busy end interrupt at step 3.c) while it has not yet been processed. This will result in mmc command to wait forever for a busy end completion that will never happen. To fix the problem, this patch implements the following changes: Considering that the mmci seems to be triggering the IRQ on both edges while monitoring DAT0 for busy completion and that same status bit is used to monitor start and end of busy detection, special care must be taken to make sure that both start and end interrupts are always cleared one after the other. 1) Clearing of card busy bit is moved in mmc_cmd_irq() function where unmasking of busy end bit is effectively handled. 2) Just before unmasking busy end event, busy start event is cleared by writing card busy bit in MMCICLEAR register. 3) Finally, once we are no more busy with a command, busy end event is cleared writing again card busy bit in MMCICLEAR register. This patch has been tested with the ST Accordo5 machine, not yet supported upstream but relies on the mmci driver. Signed-off-by: Sarang Mairal <sarang.mairal@garmin.com> Signed-off-by: Jean-Nicolas Graux <jean-nicolas.graux@st.com> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Tested-by: Ulf Hansson <ulf.hansson@linaro.org> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>