1 /* $NetBSD: wsdisplay_vcons.c,v 1.7 2006/05/14 21:47:00 elad Exp $ */ 2 3 /*- 4 * Copyright (c) 2005, 2006 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the NetBSD 18 * Foundation, Inc. and its contributors. 19 * 4. Neither the name of The NetBSD Foundation nor the names of its 20 * contributors may be used to endorse or promote products derived 21 * from this software without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 24 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 27 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 * POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 __KERNEL_RCSID(0, "$NetBSD: wsdisplay_vcons.c,v 1.7 2006/05/14 21:47:00 elad Exp $"); 38 39 #include <sys/param.h> 40 #include <sys/systm.h> 41 #include <sys/kernel.h> 42 #include <sys/buf.h> 43 #include <sys/device.h> 44 #include <sys/ioctl.h> 45 #include <sys/malloc.h> 46 #include <sys/mman.h> 47 #include <sys/tty.h> 48 #include <sys/conf.h> 49 #include <sys/proc.h> 50 #include <sys/kthread.h> 51 #include <sys/tprintf.h> 52 53 #include <dev/wscons/wsdisplayvar.h> 54 #include <dev/wscons/wsconsio.h> 55 #include <dev/wsfont/wsfont.h> 56 #include <dev/rasops/rasops.h> 57 58 #include <dev/wscons/wsdisplay_vconsvar.h> 59 60 static void vcons_dummy_init_screen(void *, struct vcons_screen *, int, 61 long *); 62 63 static int vcons_ioctl(void *, void *, u_long, caddr_t, int, struct lwp *); 64 static int vcons_alloc_screen(void *, const struct wsscreen_descr *, void **, 65 int *, int *, long *); 66 static void vcons_free_screen(void *, void *); 67 static int vcons_show_screen(void *, void *, int, void (*)(void *, int, int), 68 void *); 69 70 static void vcons_do_switch(struct vcons_data *); 71 72 /* methods that work only on text buffers */ 73 static void vcons_copycols_buffer(void *, int, int, int, int); 74 static void vcons_erasecols_buffer(void *, int, int, int, long); 75 static void vcons_copyrows_buffer(void *, int, int, int); 76 static void vcons_eraserows_buffer(void *, int, int, long); 77 static void vcons_putchar_buffer(void *, int, int, u_int, long); 78 79 /* 80 * actual wrapper methods which call both the _buffer ones above and the 81 * driver supplied ones to do the drawing 82 */ 83 static void vcons_copycols(void *, int, int, int, int); 84 static void vcons_erasecols(void *, int, int, int, long); 85 static void vcons_copyrows(void *, int, int, int); 86 static void vcons_eraserows(void *, int, int, long); 87 static void vcons_putchar(void *, int, int, u_int, long); 88 static void vcons_cursor(void *, int, int, int); 89 90 /* support for readin/writing text buffers. For wsmoused */ 91 static int vcons_putwschar(struct vcons_screen *, struct wsdisplay_char *); 92 static int vcons_getwschar(struct vcons_screen *, struct wsdisplay_char *); 93 94 static void vcons_lock(struct vcons_screen *); 95 static void vcons_unlock(struct vcons_screen *); 96 97 98 int 99 vcons_init(struct vcons_data *vd, void *cookie, struct wsscreen_descr *def, 100 struct wsdisplay_accessops *ao) 101 { 102 103 /* zero out everything so we can rely on untouched fields being 0 */ 104 memset(vd, 0, sizeof(struct vcons_data)); 105 106 vd->cookie = cookie; 107 108 vd->init_screen = vcons_dummy_init_screen; 109 vd->show_screen_cb = NULL; 110 111 /* keep a copy of the accessops that we replace below with our 112 * own wrappers */ 113 vd->ioctl = ao->ioctl; 114 115 /* configure the accessops */ 116 ao->ioctl = vcons_ioctl; 117 ao->alloc_screen = vcons_alloc_screen; 118 ao->free_screen = vcons_free_screen; 119 ao->show_screen = vcons_show_screen; 120 121 LIST_INIT(&vd->screens); 122 vd->active = NULL; 123 vd->wanted = NULL; 124 vd->currenttype = def; 125 callout_init(&vd->switch_callout); 126 127 /* 128 * a lock to serialize access to the framebuffer. 129 * when switching screens we need to make sure there's no rasops 130 * operation in progress 131 */ 132 #ifdef DIAGNOSTIC 133 vd->switch_poll_count = 0; 134 #endif 135 return 0; 136 } 137 138 static void 139 vcons_lock(struct vcons_screen *scr) 140 { 141 #ifdef VCONS_PARANOIA 142 int s; 143 144 s = splhigh(); 145 #endif 146 SCREEN_BUSY(scr); 147 #ifdef VCONS_PARANOIA 148 splx(s); 149 #endif 150 } 151 152 static void 153 vcons_unlock(struct vcons_screen *scr) 154 { 155 #ifdef VCONS_PARANOIA 156 int s; 157 158 s = splhigh(); 159 #endif 160 SCREEN_IDLE(scr); 161 #ifdef VCONS_PARANOIA 162 splx(s); 163 #endif 164 } 165 166 static void 167 vcons_dummy_init_screen(void *cookie, struct vcons_screen *scr, int exists, 168 long *defattr) 169 { 170 171 /* 172 * default init_screen() method. 173 * Needs to be overwritten so we bitch and whine in case anyone ends 174 * up in here. 175 */ 176 printf("vcons_init_screen: dummy function called. Your driver is " 177 "supposed to supply a replacement for proper operation\n"); 178 } 179 180 int 181 vcons_init_screen(struct vcons_data *vd, struct vcons_screen *scr, 182 int existing, long *defattr) 183 { 184 struct rasops_info *ri = &scr->scr_ri; 185 int cnt, i; 186 187 scr->scr_cookie = vd->cookie; 188 scr->scr_vd = scr->scr_origvd = vd; 189 SCREEN_IDLE(scr); 190 191 /* 192 * call the driver-supplied init_screen function which is expected 193 * to set up rasops_info, override cursor() and probably others 194 */ 195 vd->init_screen(vd->cookie, scr, existing, defattr); 196 197 /* 198 * save the non virtual console aware rasops and replace them with 199 * our wrappers 200 */ 201 vd->eraserows = ri->ri_ops.eraserows; 202 vd->copyrows = ri->ri_ops.copyrows; 203 vd->erasecols = ri->ri_ops.erasecols; 204 vd->copycols = ri->ri_ops.copycols; 205 vd->putchar = ri->ri_ops.putchar; 206 vd->cursor = ri->ri_ops.cursor; 207 208 ri->ri_ops.eraserows = vcons_eraserows; 209 ri->ri_ops.copyrows = vcons_copyrows; 210 ri->ri_ops.erasecols = vcons_erasecols; 211 ri->ri_ops.copycols = vcons_copycols; 212 ri->ri_ops.putchar = vcons_putchar; 213 ri->ri_ops.cursor = vcons_cursor; 214 ri->ri_hw = scr; 215 216 /* 217 * we allocate both chars and attributes in one chunk, attributes first 218 * because they have the (potentially) bigger alignment 219 */ 220 cnt = ri->ri_rows * ri->ri_cols; 221 scr->scr_attrs = (long *)malloc(cnt * (sizeof(long) + 222 sizeof(uint16_t)), M_DEVBUF, M_WAITOK); 223 if (scr->scr_attrs == NULL) 224 return ENOMEM; 225 226 scr->scr_chars = (uint16_t *)&scr->scr_attrs[cnt]; 227 228 ri->ri_ops.allocattr(ri, WS_DEFAULT_FG, WS_DEFAULT_BG, 0, defattr); 229 scr->scr_defattr = *defattr; 230 231 /* 232 * fill the attribute buffer with *defattr, chars with 0x20 233 * since we don't know if the driver tries to mimic firmware output or 234 * reset everything we do nothing to VRAM here, any driver that feels 235 * the need to clear screen or something will have to do it on its own 236 * Additional screens will start out in the background anyway so 237 * cleaning or not only really affects the initial console screen 238 */ 239 for (i = 0; i < cnt; i++) { 240 scr->scr_attrs[i] = *defattr; 241 scr->scr_chars[i] = 0x20; 242 } 243 244 if(vd->active == NULL) { 245 vd->active = scr; 246 SCREEN_VISIBLE(scr); 247 } 248 249 if (existing) { 250 SCREEN_VISIBLE(scr); 251 vd->active = scr; 252 } else { 253 SCREEN_INVISIBLE(scr); 254 } 255 256 LIST_INSERT_HEAD(&vd->screens, scr, next); 257 return 0; 258 } 259 260 static void 261 vcons_do_switch(struct vcons_data *vd) 262 { 263 struct vcons_screen *scr, *oldscr; 264 265 scr = vd->wanted; 266 if (!scr) { 267 printf("vcons_switch_screen: disappeared\n"); 268 vd->switch_cb(vd->switch_cb_arg, EIO, 0); 269 return; 270 } 271 oldscr = vd->active; /* can be NULL! */ 272 273 /* 274 * if there's an old, visible screen we mark it invisible and wait 275 * until it's not busy so we can safely switch 276 */ 277 if (oldscr != NULL) { 278 SCREEN_INVISIBLE(oldscr); 279 if (SCREEN_IS_BUSY(oldscr)) { 280 callout_reset(&vd->switch_callout, 1, 281 (void(*)(void *))vcons_do_switch, vd); 282 #ifdef DIAGNOSTIC 283 /* bitch if we wait too long */ 284 vd->switch_poll_count++; 285 if (vd->switch_poll_count > 100) { 286 panic("vcons: screen still busy"); 287 } 288 #endif 289 return; 290 } 291 /* invisible screen -> no visible cursor image */ 292 oldscr->scr_ri.ri_flg &= ~RI_CURSOR; 293 #ifdef DIAGNOSTIC 294 vd->switch_poll_count = 0; 295 #endif 296 } 297 298 if (scr == oldscr) 299 return; 300 301 #ifdef DIAGNOSTIC 302 if (SCREEN_IS_VISIBLE(scr)) 303 panic("vcons_switch_screen: already active"); 304 #endif 305 306 #ifdef notyet 307 if (vd->currenttype != type) { 308 vcons_set_screentype(vd, type); 309 vd->currenttype = type; 310 } 311 #endif 312 313 SCREEN_VISIBLE(scr); 314 vd->active = scr; 315 vd->wanted = NULL; 316 317 if (vd->show_screen_cb != NULL) 318 vd->show_screen_cb(scr); 319 320 if ((scr->scr_flags & VCONS_NO_REDRAW) == 0) 321 vcons_redraw_screen(scr); 322 323 if (vd->switch_cb) 324 vd->switch_cb(vd->switch_cb_arg, 0, 0); 325 } 326 327 void 328 vcons_redraw_screen(struct vcons_screen *scr) 329 { 330 uint16_t *charptr = scr->scr_chars; 331 long *attrptr = scr->scr_attrs; 332 struct rasops_info *ri = &scr->scr_ri; 333 int i, j, offset; 334 335 vcons_lock(scr); 336 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 337 338 /* 339 * only clear the screen when RI_FULLCLEAR is set since we're 340 * going to overwrite every single character cell anyway 341 */ 342 if (ri->ri_flg & RI_FULLCLEAR) { 343 scr->scr_vd->eraserows(ri, 0, ri->ri_rows, 344 scr->scr_defattr); 345 } 346 347 /* redraw the screen */ 348 offset = 0; 349 for (i = 0; i < ri->ri_rows; i++) { 350 for (j = 0; j < ri->ri_cols; j++) { 351 /* 352 * no need to use the wrapper function - we 353 * don't change any characters or attributes 354 * and we already made sure the screen we're 355 * working on is visible 356 */ 357 scr->scr_vd->putchar(ri, i, j, 358 charptr[offset], attrptr[offset]); 359 offset++; 360 } 361 } 362 ri->ri_flg &= ~RI_CURSOR; 363 scr->scr_vd->cursor(ri, 1, ri->ri_crow, ri->ri_ccol); 364 } 365 vcons_unlock(scr); 366 } 367 368 static int 369 vcons_ioctl(void *v, void *vs, u_long cmd, caddr_t data, int flag, 370 struct lwp *l) 371 { 372 struct vcons_data *vd = v; 373 int error; 374 375 switch (cmd) { 376 case WSDISPLAYIO_GETWSCHAR: 377 error = vcons_getwschar((struct vcons_screen *)vs, 378 (struct wsdisplay_char *)data); 379 break; 380 381 case WSDISPLAYIO_PUTWSCHAR: 382 error = vcons_putwschar((struct vcons_screen *)vs, 383 (struct wsdisplay_char *)data); 384 break; 385 386 default: 387 if (vd->ioctl != NULL) 388 error = (*vd->ioctl)(v, vs, cmd, data, flag, l); 389 else 390 error = EPASSTHROUGH; 391 } 392 393 return error; 394 } 395 396 static int 397 vcons_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep, 398 int *curxp, int *curyp, long *defattrp) 399 { 400 struct vcons_data *vd = v; 401 struct vcons_screen *scr; 402 int ret; 403 404 scr = malloc(sizeof(struct vcons_screen), M_DEVBUF, M_WAITOK | M_ZERO); 405 if (scr == NULL) 406 return ENOMEM; 407 408 scr->scr_flags = 0; 409 scr->scr_status = 0; 410 scr->scr_busy = 0; 411 scr->scr_type = type; 412 413 ret = vcons_init_screen(vd, scr, 0, defattrp); 414 if (ret != 0) { 415 free(scr, M_DEVBUF); 416 return ret; 417 } 418 419 if (vd->active == NULL) { 420 SCREEN_VISIBLE(scr); 421 vd->active = scr; 422 vd->currenttype = type; 423 } 424 425 *cookiep = scr; 426 *curxp = scr->scr_ri.ri_ccol; 427 *curyp = scr->scr_ri.ri_crow; 428 return 0; 429 } 430 431 static void 432 vcons_free_screen(void *v, void *cookie) 433 { 434 struct vcons_data *vd = v; 435 struct vcons_screen *scr = cookie; 436 437 vcons_lock(scr); 438 /* there should be no rasops activity here */ 439 440 LIST_REMOVE(scr, next); 441 442 if ((scr->scr_flags & VCONS_SCREEN_IS_STATIC) == 0) { 443 free(scr->scr_attrs, M_DEVBUF); 444 free(scr, M_DEVBUF); 445 } else { 446 /* 447 * maybe we should just restore the old rasops_info methods 448 * and free the character/attribute buffer here? 449 */ 450 #ifdef VCONS_DEBUG 451 panic("vcons_free_screen: console"); 452 #else 453 printf("vcons_free_screen: console\n"); 454 #endif 455 } 456 457 if (vd->active == scr) 458 vd->active = NULL; 459 } 460 461 static int 462 vcons_show_screen(void *v, void *cookie, int waitok, 463 void (*cb)(void *, int, int), void *cb_arg) 464 { 465 struct vcons_data *vd = v; 466 struct vcons_screen *scr; 467 468 scr = cookie; 469 if (scr == vd->active) 470 return 0; 471 472 vd->wanted = scr; 473 vd->switch_cb = cb; 474 vd->switch_cb_arg = cb_arg; 475 if (cb) { 476 callout_reset(&vd->switch_callout, 0, 477 (void(*)(void *))vcons_do_switch, vd); 478 return EAGAIN; 479 } 480 481 vcons_do_switch(vd); 482 return 0; 483 } 484 485 /* wrappers for rasops_info methods */ 486 487 static void 488 vcons_copycols_buffer(void *cookie, int row, int srccol, int dstcol, int ncols) 489 { 490 struct rasops_info *ri = cookie; 491 struct vcons_screen *scr = ri->ri_hw; 492 int from = srccol + row * ri->ri_cols; 493 int to = dstcol + row * ri->ri_cols; 494 495 memmove(&scr->scr_attrs[to], &scr->scr_attrs[from], 496 ncols * sizeof(long)); 497 memmove(&scr->scr_chars[to], &scr->scr_chars[from], 498 ncols * sizeof(uint16_t)); 499 } 500 501 static void 502 vcons_copycols(void *cookie, int row, int srccol, int dstcol, int ncols) 503 { 504 struct rasops_info *ri = cookie; 505 struct vcons_screen *scr = ri->ri_hw; 506 507 vcons_copycols_buffer(cookie, row, srccol, dstcol, ncols); 508 509 vcons_lock(scr); 510 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 511 scr->scr_vd->copycols(cookie, row, srccol, dstcol, ncols); 512 } 513 vcons_unlock(scr); 514 } 515 516 static void 517 vcons_erasecols_buffer(void *cookie, int row, int startcol, int ncols, long fillattr) 518 { 519 struct rasops_info *ri = cookie; 520 struct vcons_screen *scr = ri->ri_hw; 521 int start = startcol + row * ri->ri_cols; 522 int end = start + ncols, i; 523 524 for (i = start; i < end; i++) { 525 scr->scr_attrs[i] = fillattr; 526 scr->scr_chars[i] = 0x20; 527 } 528 } 529 530 static void 531 vcons_erasecols(void *cookie, int row, int startcol, int ncols, long fillattr) 532 { 533 struct rasops_info *ri = cookie; 534 struct vcons_screen *scr = ri->ri_hw; 535 536 vcons_erasecols_buffer(cookie, row, startcol, ncols, fillattr); 537 538 vcons_lock(scr); 539 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 540 scr->scr_vd->erasecols(cookie, row, startcol, ncols, 541 fillattr); 542 } 543 vcons_unlock(scr); 544 } 545 546 static void 547 vcons_copyrows_buffer(void *cookie, int srcrow, int dstrow, int nrows) 548 { 549 struct rasops_info *ri = cookie; 550 struct vcons_screen *scr = ri->ri_hw; 551 int from, to, len; 552 553 from = ri->ri_cols * srcrow; 554 to = ri->ri_cols * dstrow; 555 len = ri->ri_cols * nrows; 556 557 memmove(&scr->scr_attrs[to], &scr->scr_attrs[from], 558 len * sizeof(long)); 559 memmove(&scr->scr_chars[to], &scr->scr_chars[from], 560 len * sizeof(uint16_t)); 561 } 562 563 static void 564 vcons_copyrows(void *cookie, int srcrow, int dstrow, int nrows) 565 { 566 struct rasops_info *ri = cookie; 567 struct vcons_screen *scr = ri->ri_hw; 568 569 vcons_copyrows_buffer(cookie, srcrow, dstrow, nrows); 570 571 vcons_lock(scr); 572 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 573 scr->scr_vd->copyrows(cookie, srcrow, dstrow, nrows); 574 } 575 vcons_unlock(scr); 576 } 577 578 static void 579 vcons_eraserows_buffer(void *cookie, int row, int nrows, long fillattr) 580 { 581 struct rasops_info *ri = cookie; 582 struct vcons_screen *scr = ri->ri_hw; 583 int start, end, i; 584 585 start = ri->ri_cols * row; 586 end = ri->ri_cols * (row + nrows); 587 588 for (i = start; i < end; i++) { 589 scr->scr_attrs[i] = fillattr; 590 scr->scr_chars[i] = 0x20; 591 } 592 } 593 594 static void 595 vcons_eraserows(void *cookie, int row, int nrows, long fillattr) 596 { 597 struct rasops_info *ri = cookie; 598 struct vcons_screen *scr = ri->ri_hw; 599 600 vcons_eraserows_buffer(cookie, row, nrows, fillattr); 601 602 vcons_lock(scr); 603 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 604 scr->scr_vd->eraserows(cookie, row, nrows, fillattr); 605 } 606 vcons_unlock(scr); 607 } 608 609 static void 610 vcons_putchar_buffer(void *cookie, int row, int col, u_int c, long attr) 611 { 612 struct rasops_info *ri = cookie; 613 struct vcons_screen *scr = ri->ri_hw; 614 int pos; 615 616 if ((row >= 0) && (row < ri->ri_rows) && (col >= 0) && 617 (col < ri->ri_cols)) { 618 pos = col + row * ri->ri_cols; 619 scr->scr_attrs[pos] = attr; 620 scr->scr_chars[pos] = c; 621 } 622 } 623 624 static void 625 vcons_putchar(void *cookie, int row, int col, u_int c, long attr) 626 { 627 struct rasops_info *ri = cookie; 628 struct vcons_screen *scr = ri->ri_hw; 629 630 vcons_putchar_buffer(cookie, row, col, c, attr); 631 632 vcons_lock(scr); 633 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 634 scr->scr_vd->putchar(cookie, row, col, c, attr); 635 } 636 vcons_unlock(scr); 637 } 638 639 static void 640 vcons_cursor(void *cookie, int on, int row, int col) 641 { 642 struct rasops_info *ri = cookie; 643 struct vcons_screen *scr = ri->ri_hw; 644 645 vcons_lock(scr); 646 if (SCREEN_IS_VISIBLE(scr) && SCREEN_CAN_DRAW(scr)) { 647 scr->scr_vd->cursor(cookie, on, row, col); 648 } else { 649 scr->scr_ri.ri_crow = row; 650 scr->scr_ri.ri_ccol = col; 651 } 652 vcons_unlock(scr); 653 } 654 655 /* methods to read/write characters via ioctl() */ 656 657 static int 658 vcons_putwschar(struct vcons_screen *scr, struct wsdisplay_char *wsc) 659 { 660 long attr; 661 struct rasops_info *ri; 662 663 KASSERT(scr != NULL && wsc != NULL); 664 665 ri = &scr->scr_ri; 666 667 ri->ri_ops.allocattr(ri, wsc->foreground, wsc->background, 668 wsc->flags, &attr); 669 vcons_putchar(ri, wsc->row, wsc->col, wsc->letter, attr); 670 return 0; 671 } 672 673 static int 674 vcons_getwschar(struct vcons_screen *scr, struct wsdisplay_char *wsc) 675 { 676 int offset; 677 long attr; 678 struct rasops_info *ri; 679 680 KASSERT(scr != NULL && wsc != NULL); 681 682 ri = &scr->scr_ri; 683 offset = ri->ri_cols * wsc->row + wsc->col; 684 wsc->letter = scr->scr_chars[offset]; 685 attr = scr->scr_attrs[offset]; 686 687 /* 688 * this is ugly. We need to break up an attribute into colours and 689 * flags but there's no rasops method to do that so we must rely on 690 * the 'canonical' encoding. 691 */ 692 wsc->foreground = (attr & 0xff000000) >> 24; 693 wsc->background = (attr & 0x00ff0000) >> 16; 694 wsc->flags = (attr & 0x0000ff00) >> 8; 695 return 0; 696 } 697