Login
[x]
Log in using an account from:
Fedora Account System
Red Hat Associate
Red Hat Customer
Or login using a Red Hat Bugzilla account
Forgot Password
Login:
Hide Forgot
Create an Account
Red Hat Bugzilla – Attachment 291431 Details for
Bug 428231
Switch Xen PVFB over to use QEMU instead of libvncserver & merge TLS patches
[?]
New
Simple Search
Advanced Search
My Links
Browse
Requests
Reports
Current State
Search
Tabular reports
Graphical reports
Duplicates
Other Reports
User Changes
Plotly Reports
Bug Status
Bug Severity
Non-Defaults
|
Product Dashboard
Help
Page Help!
Bug Writing Guidelines
What's new
Browser Support Policy
5.0.4.rh83 Release notes
FAQ
Guides index
User guide
Web Services
Contact
Legal
This site requires JavaScript to be enabled to function correctly, please enable it.
[patch]
Add QEMU pv machine type
xen-pvfb-02-qemu-pv-machine.patch (text/plain), 18.31 KB, created by
Daniel Berrangé
on 2008-01-11 22:52:08 UTC
(
hide
)
Description:
Add QEMU pv machine type
Filename:
MIME Type:
Creator:
Daniel Berrangé
Created:
2008-01-11 22:52:08 UTC
Size:
18.31 KB
patch
obsolete
>diff -rupN xen-3.1.0-src.orig/tools/ioemu/hw/xen_machine_pv.c xen-3.1.0-src.new/tools/ioemu/hw/xen_machine_pv.c >--- xen-3.1.0-src.orig/tools/ioemu/hw/xen_machine_pv.c 1969-12-31 19:00:00.000000000 -0500 >+++ xen-3.1.0-src.new/tools/ioemu/hw/xen_machine_pv.c 2008-01-11 16:36:11.000000000 -0500 >@@ -0,0 +1,231 @@ >+/* >+ * QEMU Xen PV Machine >+ * >+ * Copyright (c) 2007 Red Hat >+ * >+ * Permission is hereby granted, free of charge, to any person obtaining a copy >+ * of this software and associated documentation files (the "Software"), to deal >+ * in the Software without restriction, including without limitation the rights >+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell >+ * copies of the Software, and to permit persons to whom the Software is >+ * furnished to do so, subject to the following conditions: >+ * >+ * The above copyright notice and this permission notice shall be included in >+ * all copies or substantial portions of the Software. >+ * >+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR >+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, >+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL >+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER >+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, >+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN >+ * THE SOFTWARE. >+ */ >+ >+#include "vl.h" >+#include "../../xenfb/xenfb.h" >+#include <linux/input.h> >+ >+/* A convenient function for munging pixels between different depths */ >+#define BLT(SRC_T,DST_T,RLS,GLS,BLS,RRS,GRS,BRS,RM,GM,BM) \ >+ for (line = y ; line < h ; line++) { \ >+ SRC_T *src = (SRC_T *)(xenfb->pixels + (line*xenfb->row_stride) + (x*xenfb->depth/8)); \ >+ DST_T *dst = (DST_T *)(ds->data + (line*ds->linesize) + (x*ds->depth/8)); \ >+ int col; \ >+ for (col = x ; col < w ; col++) { \ >+ *dst = (((*src >> RRS)&RM) << RLS) | \ >+ (((*src >> GRS)&GM) << GLS) | \ >+ (((*src >> GRS)&BM) << BLS); \ >+ src++; \ >+ dst++; \ >+ } \ >+ } >+ >+ >+/* This copies data from the guest framebuffer region, into QEMU's copy >+ * NB. QEMU's copy is stored in the pixel format of a) the local X server (SDL case) >+ * or b) the current VNC client pixel format. >+ */ >+static void xen_pvfb_guest_copy(struct xenfb *xenfb, int x, int y, int w, int h) >+{ >+ DisplayState *ds = (DisplayState *)xenfb->user_data; >+ int line; >+ >+ if (xenfb->depth == ds->depth) { /* Perfect match can use fast path */ >+ for (line = y ; line < (y+h) ; line++) { >+ memcpy(ds->data + (line * ds->linesize) + (x*ds->depth/8), >+ xenfb->pixels + (line*xenfb->row_stride) + (x*xenfb->depth/8), >+ w * xenfb->depth/8); >+ } >+ } else { /* Mismatch requires slow pixel munging */ >+ if (xenfb->depth == 8) { >+ /* 8 bit source == r:3 g:3 b:2 */ >+ if (ds->depth == 16) { >+ BLT(uint8_t, uint16_t, 5, 2, 0, 11, 5, 0, 7, 7, 3); >+ } else if (ds->depth == 32) { >+ BLT(uint8_t, uint32_t, 5, 2, 0, 16, 8, 0, 7, 7, 3); >+ } >+ } else if (xenfb->depth == 16) { >+ /* 16 bit source == r:5 g:6 b:5 */ >+ if (ds->depth == 8) { >+ BLT(uint16_t, uint8_t, 11, 5, 0, 5, 2, 0, 31, 63, 31); >+ } else if (ds->depth == 32) { >+ BLT(uint16_t, uint32_t, 11, 5, 0, 16, 8, 0, 31, 63, 31); >+ } >+ } else if (xenfb->depth == 32) { >+ /* 32 bit source == r:8 g:8 b:8 (padding:8) */ >+ if (ds->depth == 8) { >+ BLT(uint32_t, uint8_t, 16, 8, 0, 5, 2, 0, 255, 255, 255); >+ } else if (ds->depth == 16) { >+ BLT(uint32_t, uint16_t, 16, 8, 0, 11, 5, 0, 255, 255, 255); >+ } >+ } >+ } >+ dpy_update(ds, x, y, w, h); >+} >+ >+ >+/* Send a keypress from the client to the guest OS */ >+static void xen_pvfb_put_keycode(void *opaque, int keycode) >+{ >+ struct xenfb *xenfb = (struct xenfb*)opaque; >+ xenfb_send_key(xenfb, keycode & 0x80 ? 0 : 1, keycode & 0x7f); >+} >+ >+/* Send a mouse event from the client to the guest OS */ >+static void xen_pvfb_mouse_event(void *opaque, >+ int dx, int dy, int dz, int button_state) >+{ >+ static int old_state = 0; >+ int i; >+ struct xenfb *xenfb = (struct xenfb*)opaque; >+ DisplayState *ds = (DisplayState *)xenfb->user_data; >+ if (xenfb->abs_pointer_wanted) >+ xenfb_send_position(xenfb, >+ dx*ds->width/0x7fff, >+ dy*ds->height/0x7fff); >+ else >+ xenfb_send_motion(xenfb, dx, dy); >+ >+ for (i = 0 ; i < 8 ; i++) { >+ int lastDown = old_state & (1 << i); >+ int down = button_state & (1 << i); >+ if (down == lastDown) >+ continue; >+ >+ if (xenfb_send_key(xenfb, down, BTN_LEFT+i) < 0) >+ return; >+ } >+ old_state = button_state; >+} >+ >+/* QEMU display state changed, so refresh the framebuffer copy */ >+void xen_pvfb_update(void *opaque) >+{ >+ struct xenfb *xenfb = (struct xenfb *)opaque; >+ xen_pvfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height); >+} >+ >+/* QEMU display state changed, so refresh the framebuffer copy */ >+void xen_pvfb_invalidate(void *opaque) >+{ >+ struct xenfb *xenfb = (struct xenfb *)opaque; >+ xen_pvfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height); >+} >+ >+/* Screen dump is not used in Xen, so no need to impl this ? */ >+void xen_pvfb_screen_dump(void *opaque, const char *name) { } >+ >+void xen_pvfb_dispatch_store(void *opaque) { >+ int ret; >+ if ((ret = xenfb_dispatch_store((struct xenfb *)opaque)) < 0) { >+ fprintf(stderr, "Failure while dispatching store: %d\n", ret); >+ exit(1); >+ } >+} >+ >+void xen_pvfb_dispatch_channel(void *opaque) { >+ int ret; >+ if ((ret = xenfb_dispatch_channel((struct xenfb *)opaque)) < 0) { >+ fprintf(stderr, "Failure while dispatching store: %d\n", ret); >+ exit(1); >+ } >+} >+ >+/* The Xen PV machine currently provides >+ * - a virtual framebuffer >+ * - .... >+ */ >+static void xen_init_pv(uint64_t ram_size, int vga_ram_size, char *boot_device, >+ DisplayState *ds, const char **fd_filename, >+ int snapshot, >+ const char *kernel_filename, >+ const char *kernel_cmdline, >+ const char *initrd_filename, time_t timeoffset) >+{ >+ struct xenfb *xenfb; >+ extern int domid; >+ int fd; >+ >+ /* Prepare PVFB state */ >+ xenfb = xenfb_new(); >+ if (xenfb == NULL) { >+ fprintf(stderr, "Could not create framebuffer (%s)\n", >+ strerror(errno)); >+ exit(1); >+ } >+ >+ /* Talk to the guest */ >+ if (xenfb_attach_dom(xenfb, domid) < 0) { >+ fprintf(stderr, "Could not connect to domain (%s)\n", >+ strerror(errno)); >+ exit(1); >+ } >+ xenfb->update = xen_pvfb_guest_copy; >+ xenfb->user_data = ds; >+ >+ /* Tell QEMU to allocate a graphical console */ >+ graphic_console_init(ds, >+ xen_pvfb_update, >+ xen_pvfb_invalidate, >+ xen_pvfb_screen_dump, >+ xenfb); >+ >+ /* Register our keyboard & mouse handlers */ >+ qemu_add_kbd_event_handler(xen_pvfb_put_keycode, xenfb); >+ qemu_add_mouse_event_handler(xen_pvfb_mouse_event, xenfb, >+ xenfb->abs_pointer_wanted >+ ); >+ >+ /* Listen for events from xenstore */ >+ fd = xenfb_get_store_fd(xenfb); >+ if (qemu_set_fd_handler2(fd, NULL, xen_pvfb_dispatch_store, NULL, xenfb) < 0) { >+ fprintf(stderr, "Could not register event handler (%s)\n", >+ strerror(errno)); >+ } >+ >+ /* Listen for events from the event channel */ >+ fd = xenfb_get_channel_fd(xenfb); >+ if (qemu_set_fd_handler2(fd, NULL, xen_pvfb_dispatch_channel, NULL, xenfb) < 0) { >+ fprintf(stderr, "Could not register event handler (%s)\n", >+ strerror(errno)); >+ } >+ >+ /* Setup QEMU display */ >+ dpy_resize(ds, xenfb->width, xenfb->height); >+} >+ >+QEMUMachine xenpv_machine = { >+ "xenpv", >+ "Xen Para-virtualized PC", >+ xen_init_pv, >+}; >+ >+/* >+ * Local variables: >+ * indent-tabs-mode: nil >+ * c-indent-level: 4 >+ * c-basic-offset: 4 >+ * tab-width: 4 >+ * End: >+ */ >diff -rupN xen-3.1.0-src.orig/tools/ioemu/Makefile.target xen-3.1.0-src.new/tools/ioemu/Makefile.target >--- xen-3.1.0-src.orig/tools/ioemu/Makefile.target 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/ioemu/Makefile.target 2008-01-11 16:36:11.000000000 -0500 >@@ -370,6 +370,8 @@ VL_OBJS+= piix4acpi.o > VL_OBJS+= xenstore.o > VL_OBJS+= xen_platform.o > VL_OBJS+= xen_machine_fv.o >+VL_OBJS+= xen_machine_pv.o >+VL_OBJS+= ../../xenfb/xenfb.o > VL_OBJS+= tpm_tis.o > DEFINES += -DHAS_AUDIO > endif >diff -rupN xen-3.1.0-src.orig/tools/ioemu/target-i386-dm/helper2.c xen-3.1.0-src.new/tools/ioemu/target-i386-dm/helper2.c >--- xen-3.1.0-src.orig/tools/ioemu/target-i386-dm/helper2.c 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/ioemu/target-i386-dm/helper2.c 2008-01-11 16:36:50.000000000 -0500 >@@ -615,14 +615,15 @@ int main_loop(void) > extern int shutdown_requested; > extern int suspend_requested; > CPUState *env = cpu_single_env; >- int evtchn_fd = xc_evtchn_fd(xce_handle); >+ int evtchn_fd = xce_handle == -1 ? -1 : xc_evtchn_fd(xce_handle); > char qemu_file[PATH_MAX]; > > buffered_io_timer = qemu_new_timer(rt_clock, handle_buffered_io, > cpu_single_env); > qemu_mod_timer(buffered_io_timer, qemu_get_clock(rt_clock)); > >- qemu_set_fd_handler(evtchn_fd, cpu_handle_ioreq, NULL, env); >+ if (evtchn_fd != -1) >+ qemu_set_fd_handler(evtchn_fd, cpu_handle_ioreq, NULL, env); > > while (!(vm_running && suspend_requested)) > /* Wait up to 10 msec. */ >diff -rupN xen-3.1.0-src.orig/tools/ioemu/vl.c xen-3.1.0-src.new/tools/ioemu/vl.c >--- xen-3.1.0-src.orig/tools/ioemu/vl.c 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/ioemu/vl.c 2008-01-11 16:36:11.000000000 -0500 >@@ -168,7 +168,7 @@ int xc_handle; > > time_t timeoffset = 0; > >-char domain_name[1024] = { 'H','V', 'M', 'X', 'E', 'N', '-'}; >+char domain_name[1024] = "Xen-no-name"; > extern int domid; > > char vncpasswd[64]; >@@ -5698,6 +5698,7 @@ void register_machines(void) > qemu_register_machine(&isapc_machine); > #else > qemu_register_machine(&xenfv_machine); >+ qemu_register_machine(&xenpv_machine); > #endif > #elif defined(TARGET_PPC) > qemu_register_machine(&heathrow_machine); >@@ -6398,7 +6399,8 @@ int main(int argc, char **argv) > acpi_enabled = 0; > break; > case QEMU_OPTION_domainname: >- strncat(domain_name, optarg, sizeof(domain_name) - 20); >+ snprintf(domain_name, sizeof(domain_name), >+ "Xen-%s", optarg); > break; > case QEMU_OPTION_d: > domid = atoi(optarg); >diff -rupN xen-3.1.0-src.orig/tools/ioemu/vl.h xen-3.1.0-src.new/tools/ioemu/vl.h >--- xen-3.1.0-src.orig/tools/ioemu/vl.h 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/ioemu/vl.h 2008-01-11 16:36:11.000000000 -0500 >@@ -968,6 +968,7 @@ extern QEMUMachine pc_machine; > extern QEMUMachine isapc_machine; > #ifdef CONFIG_DM > extern QEMUMachine xenfv_machine; >+extern QEMUMachine xenpv_machine; > #endif > extern int fd_bootchk; > >diff -rupN xen-3.1.0-src.orig/tools/python/xen/xend/server/vfbif.py xen-3.1.0-src.new/tools/python/xen/xend/server/vfbif.py >--- xen-3.1.0-src.orig/tools/python/xen/xend/server/vfbif.py 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/python/xen/xend/server/vfbif.py 2008-01-11 16:36:11.000000000 -0500 >@@ -7,6 +7,7 @@ import xen.xend > import os > > def spawn_detached(path, args, env): >+ log.debug("Spawn: " + str(args)) > p = os.fork() > if p == 0: > os.spawnve(os.P_NOWAIT, path, args, env) >@@ -48,8 +49,10 @@ class VfbifController(DevController): > # old frontend compatibility > self.vm.writeDom("console/use_graphics", "1") > # /old >- std_args = [ "--domid", "%d" % self.vm.getDomid(), >- "--title", self.vm.getName() ] >+ args = [ xen.util.auxbin.pathTo("qemu-dm"), >+ "-M", "xenpv", >+ "-d", "%d" % self.vm.getDomid(), >+ "-domain-name", self.vm.getName() ] > t = sxp.child_value(config, "type") > if t == "vnc": > passwd = None >@@ -63,18 +66,17 @@ class VfbifController(DevController): > else: > log.debug("No VNC passwd configured for vfb access") > >- # Try to start the vnc backend >- args = [xen.util.auxbin.pathTo("xen-vncfb")] >- if sxp.child_value(config, "vncunused") is not None: >- args += ["--unused"] >- elif sxp.child_value(config, "vncdisplay") is not None: >- args += ["--vncport", "%d" % (5900 + int(sxp.child_value(config, "vncdisplay")))] >- vnclisten = sxp.child_value(config, 'vnclisten', >+ vnclisten = sxp.child_value(config, 'vnclisten', > xen.xend.XendRoot.instance().get_vnclisten_address()) >- args += [ "--listen", vnclisten ] >+ vncdisplay = sxp.child_value(config, 'vncdisplay', 0) >+ args += ['-vnc', "%s:%d" % (vnclisten, int(vncdisplay))] >+ >+ if sxp.child_value(config, 'vncunused', 0): >+ args += ['-vncunused'] >+ > if sxp.child_value(config, "keymap") is not None: > args += ["-k", "%s" % sxp.child_value(config, "keymap")] >- spawn_detached(args[0], args + std_args, os.environ) >+ spawn_detached(args[0], args, os.environ) > elif t == "sdl": > args = [xen.util.auxbin.pathTo("xen-sdlfb")] > env = dict(os.environ) >@@ -82,7 +84,7 @@ class VfbifController(DevController): > env['DISPLAY'] = sxp.child_value(config, "display") > if sxp.child_value(config, "xauthority") is not None: > env['XAUTHORITY'] = sxp.child_value(config, "xauthority") >- spawn_detached(args[0], args + std_args, env) >+ spawn_detached(args[0], args, env) > else: > raise VmError('Unknown vfb type %s (%s)' % (t, repr(config))) > >diff -rupN xen-3.1.0-src.orig/tools/xenfb/xenfb.c xen-3.1.0-src.new/tools/xenfb/xenfb.c >--- xen-3.1.0-src.orig/tools/xenfb/xenfb.c 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/xenfb/xenfb.c 2008-01-11 16:36:11.000000000 -0500 >@@ -757,37 +757,58 @@ static int xenfb_on_state_change(struct > return 0; > } > >-/* Returns 0 normally, -1 on error, or -2 if the domain went away. */ >-int xenfb_poll(struct xenfb *xenfb_pub, fd_set *readfds) >+int xenfb_dispatch_channel(struct xenfb *xenfb_pub) > { > struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; > evtchn_port_t port; >+ port = xc_evtchn_pending(xenfb->evt_xch); >+ if (port == -1) >+ return -1; >+ >+ if (port == xenfb->fb.port) >+ xenfb_on_fb_event(xenfb); >+ else if (port == xenfb->kbd.port) >+ xenfb_on_kbd_event(xenfb); >+ >+ if (xc_evtchn_unmask(xenfb->evt_xch, port) == -1) >+ return -1; >+ >+ return 0; >+} >+ >+int xenfb_dispatch_store(struct xenfb *xenfb_pub) >+{ >+ struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; > unsigned dummy; > char **vec; > int r; > >- if (FD_ISSET(xc_evtchn_fd(xenfb->evt_xch), readfds)) { >- port = xc_evtchn_pending(xenfb->evt_xch); >- if (port == -1) >- return -1; >- >- if (port == xenfb->fb.port) >- xenfb_on_fb_event(xenfb); >- else if (port == xenfb->kbd.port) >- xenfb_on_kbd_event(xenfb); >+ vec = xs_read_watch(xenfb->xsh, &dummy); >+ free(vec); >+ r = xenfb_on_state_change(&xenfb->fb); >+ if (r == 0) >+ r = xenfb_on_state_change(&xenfb->kbd); >+ if (r == -1) >+ return -2; >+ >+ return 0; >+} > >- if (xc_evtchn_unmask(xenfb->evt_xch, port) == -1) >- return -1; >+ >+/* Returns 0 normally, -1 on error, or -2 if the domain went away. */ >+int xenfb_poll(struct xenfb *xenfb_pub, fd_set *readfds) >+{ >+ struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; >+ int ret; >+ >+ if (FD_ISSET(xc_evtchn_fd(xenfb->evt_xch), readfds)) { >+ if ((ret = xenfb_dispatch_channel(xenfb_pub)) < 0) >+ return ret; > } > > if (FD_ISSET(xs_fileno(xenfb->xsh), readfds)) { >- vec = xs_read_watch(xenfb->xsh, &dummy); >- free(vec); >- r = xenfb_on_state_change(&xenfb->fb); >- if (r == 0) >- r = xenfb_on_state_change(&xenfb->kbd); >- if (r == -1) >- return -2; >+ if ((ret = xenfb_dispatch_store(xenfb_pub)) < 0) >+ return ret; > } > > return 0; >@@ -804,6 +825,18 @@ int xenfb_select_fds(struct xenfb *xenfb > return fd1 > fd2 ? fd1 + 1 : fd2 + 1; > } > >+int xenfb_get_store_fd(struct xenfb *xenfb_pub) >+{ >+ struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; >+ return xs_fileno(xenfb->xsh); >+} >+ >+int xenfb_get_channel_fd(struct xenfb *xenfb_pub) >+{ >+ struct xenfb_private *xenfb = (struct xenfb_private *)xenfb_pub; >+ return xc_evtchn_fd(xenfb->evt_xch); >+} >+ > static int xenfb_kbd_event(struct xenfb_private *xenfb, > union xenkbd_in_event *event) > { >@@ -864,3 +897,10 @@ int xenfb_send_position(struct xenfb *xe > > return xenfb_kbd_event(xenfb, &event); > } >+/* >+ * Local variables: >+ * c-indent-level: 8 >+ * c-basic-offset: 8 >+ * tab-width: 8 >+ * End: >+ */ >diff -rupN xen-3.1.0-src.orig/tools/xenfb/xenfb.h xen-3.1.0-src.new/tools/xenfb/xenfb.h >--- xen-3.1.0-src.orig/tools/xenfb/xenfb.h 2008-01-11 16:35:48.000000000 -0500 >+++ xen-3.1.0-src.new/tools/xenfb/xenfb.h 2008-01-11 16:36:11.000000000 -0500 >@@ -25,8 +25,12 @@ void xenfb_teardown(struct xenfb *xenfb) > > int xenfb_attach_dom(struct xenfb *xenfb, int domid); > >+int xenfb_dispatch_store(struct xenfb *xenfb_pub); >+int xenfb_dispatch_channel(struct xenfb *xenfb_pub); > int xenfb_select_fds(struct xenfb *xenfb, fd_set *readfds); > int xenfb_poll(struct xenfb *xenfb, fd_set *readfds); >+int xenfb_get_store_fd(struct xenfb *xenfb_pub); >+int xenfb_get_channel_fd(struct xenfb *xenfb_pub); > > int xenfb_send_key(struct xenfb *xenfb, bool down, int keycode); > int xenfb_send_motion(struct xenfb *xenfb, int rel_x, int rel_y);
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 428231
:
291430
| 291431 |
291432
|
291433
|
291434
|
291435
|
291473
|
291474