diff options
author | Aaron Durbin <adurbin@chromium.org> | 2015-05-15 13:15:34 -0500 |
---|---|---|
committer | Aaron Durbin <adurbin@chromium.org> | 2015-05-26 22:32:47 +0200 |
commit | c6588c5af94e568bddd8111c3fca736f464042cf (patch) | |
tree | 0efa16d6948cfde369932fd18dec46102f807c2f /src/soc/samsung | |
parent | def0fb57dfd91e6599c622a7b2769164a5c02ef0 (diff) | |
download | coreboot-c6588c5af94e568bddd8111c3fca736f464042cf.tar.xz |
coreboot: introduce boot_device
The boot_device is a region_device that represents the
device from which coreboot retrieves and boots its stages.
The existing cbfs implementations use the boot_device as
the intermediary for accessing the CBFS region. Also,
there's currently only support for a read-only view of
the boot_device. i.e. one cannot write to the boot_device
using this view. However, a writable boot_device could
be added in the future.
Change-Id: Ic0da796ab161b8025c90631be3423ba6473ad31c
Signed-off-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/10216
Tested-by: build bot (Jenkins)
Tested-by: Raptor Engineering Automated Test Stand <noreply@raptorengineeringinc.com>
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
Diffstat (limited to 'src/soc/samsung')
-rw-r--r-- | src/soc/samsung/exynos5250/alternate_cbfs.c | 118 | ||||
-rw-r--r-- | src/soc/samsung/exynos5250/include/soc/spi.h | 9 | ||||
-rw-r--r-- | src/soc/samsung/exynos5250/spi.c | 85 | ||||
-rw-r--r-- | src/soc/samsung/exynos5420/alternate_cbfs.c | 118 | ||||
-rw-r--r-- | src/soc/samsung/exynos5420/include/soc/spi.h | 9 | ||||
-rw-r--r-- | src/soc/samsung/exynos5420/spi.c | 78 |
6 files changed, 223 insertions, 194 deletions
diff --git a/src/soc/samsung/exynos5250/alternate_cbfs.c b/src/soc/samsung/exynos5250/alternate_cbfs.c index 2e2aeec562..546018a35a 100644 --- a/src/soc/samsung/exynos5250/alternate_cbfs.c +++ b/src/soc/samsung/exynos5250/alternate_cbfs.c @@ -19,6 +19,7 @@ #include <assert.h> +#include <boot_device.h> #include <cbfs.h> /* This driver serves as a CBFS media source. */ #include <console/console.h> #include <soc/alternate_cbfs.h> @@ -45,7 +46,7 @@ * rest of the firmware's lifetime and all subsequent stages (which will not * have __PRE_RAM__ defined) can just directly reference it there. */ -static int usb_cbfs_open(struct cbfs_media *media) +static int usb_cbfs_open(void) { #ifdef __PRE_RAM__ static int first_run = 1; @@ -80,7 +81,7 @@ static int usb_cbfs_open(struct cbfs_media *media) * this seems like a safer approach. It also makes it easy to pass our image * down to payloads. */ -static int sdmmc_cbfs_open(struct cbfs_media *media) +static int sdmmc_cbfs_open(void) { #ifdef __PRE_RAM__ /* @@ -111,66 +112,109 @@ static int sdmmc_cbfs_open(struct cbfs_media *media) return 0; } -static int alternate_cbfs_close(struct cbfs_media *media) { return 0; } +static int exynos_cbfs_open(struct cbfs_media *media) { + return 0; +} + +static int exynos_cbfs_close(struct cbfs_media *media) { + return 0; +} + +static size_t exynos_cbfs_read(struct cbfs_media *media, void *dest, + size_t offset, size_t count) { + const struct region_device *boot_dev; + + boot_dev = media->context; + + if (rdev_readat(boot_dev, dest, offset, count) < 0) + return 0; -static size_t alternate_cbfs_read(struct cbfs_media *media, void *dest, - size_t offset, size_t count) -{ - ASSERT(offset + count < _cbfs_cache_size); - memcpy(dest, _cbfs_cache + offset, count); return count; } -static void *alternate_cbfs_map(struct cbfs_media *media, size_t offset, - size_t count) -{ - ASSERT(offset + count < _cbfs_cache_size); - return _cbfs_cache + offset; +static void *exynos_cbfs_map(struct cbfs_media *media, size_t offset, + size_t count) { + const struct region_device *boot_dev; + void *ptr; + + boot_dev = media->context; + + ptr = rdev_mmap(boot_dev, offset, count); + + if (ptr == NULL) + return (void *)-1; + + return ptr; } -static void *alternate_cbfs_unmap(struct cbfs_media *media, - const void *buffer) { return 0; } +static void *exynos_cbfs_unmap(struct cbfs_media *media, + const void *address) { + const struct region_device *boot_dev; -static int initialize_exynos_sdmmc_cbfs_media(struct cbfs_media *media) -{ - printk(BIOS_DEBUG, "Using Exynos alternate boot mode SDMMC\n"); + boot_dev = media->context; - media->open = sdmmc_cbfs_open; - media->close = alternate_cbfs_close; - media->read = alternate_cbfs_read; - media->map = alternate_cbfs_map; - media->unmap = alternate_cbfs_unmap; + rdev_munmap(boot_dev, (void *)address); - return 0; + return NULL; } -static int initialize_exynos_usb_cbfs_media(struct cbfs_media *media) +int init_default_cbfs_media(struct cbfs_media *media) { - printk(BIOS_DEBUG, "Using Exynos alternate boot mode USB A-A\n"); + boot_device_init(); - media->open = usb_cbfs_open; - media->close = alternate_cbfs_close; - media->read = alternate_cbfs_read; - media->map = alternate_cbfs_map; - media->unmap = alternate_cbfs_unmap; + media->context = (void *)boot_device_ro(); + + if (media->context == NULL) + return -1; + + media->open = exynos_cbfs_open; + media->close = exynos_cbfs_close; + media->read = exynos_cbfs_read; + media->map = exynos_cbfs_map; + media->unmap = exynos_cbfs_unmap; return 0; } -int init_default_cbfs_media(struct cbfs_media *media) +static struct mem_region_device alternate_rdev = MEM_REGION_DEV_INIT(NULL, 0); + +const struct region_device *boot_device_ro(void) { if (*iram_secondary_base == SECONDARY_BASE_BOOT_USB) - return initialize_exynos_usb_cbfs_media(media); + return &alternate_rdev.rdev; + + switch (exynos_power->om_stat & OM_STAT_MASK) { + case OM_STAT_SDMMC: + return &alternate_rdev.rdev; + case OM_STAT_SPI: + return exynos_spi_boot_device(); + default: + printk(BIOS_EMERG, "Exynos OM_STAT value 0x%x not supported!\n", + exynos_power->om_stat); + return NULL; + } +} + +void boot_device_init(void) +{ + mem_region_device_init(&alternate_rdev, _cbfs_cache, _cbfs_cache_size); + + if (*iram_secondary_base == SECONDARY_BASE_BOOT_USB) { + printk(BIOS_DEBUG, "Using Exynos alternate boot mode USB A-A\n"); + usb_cbfs_open(); + return; + } switch (exynos_power->om_stat & OM_STAT_MASK) { case OM_STAT_SDMMC: - return initialize_exynos_sdmmc_cbfs_media(media); + printk(BIOS_DEBUG, "Using Exynos alternate boot mode SDMMC\n"); + sdmmc_cbfs_open(); + break; case OM_STAT_SPI: - return initialize_exynos_spi_cbfs_media(media, - _cbfs_cache, _cbfs_cache_size); + exynos_init_spi_boot_device(); + break; default: printk(BIOS_EMERG, "Exynos OM_STAT value 0x%x not supported!\n", exynos_power->om_stat); - return 1; } } diff --git a/src/soc/samsung/exynos5250/include/soc/spi.h b/src/soc/samsung/exynos5250/include/soc/spi.h index 92236aecba..298db1a851 100644 --- a/src/soc/samsung/exynos5250/include/soc/spi.h +++ b/src/soc/samsung/exynos5250/include/soc/spi.h @@ -20,8 +20,7 @@ #ifndef CPU_SAMSUNG_EXYNOS5250_SPI_H #define CPU_SAMSUNG_EXYNOS5250_SPI_H -/* This driver serves as a CBFS media source. */ -#include <cbfs.h> +#include <boot_device.h> /* SPI peripheral register map; padded to 64KB */ struct exynos_spi { @@ -92,8 +91,6 @@ int exynos_spi_open(struct exynos_spi *regs); int exynos_spi_read(struct exynos_spi *regs, void *dest, u32 len, u32 off); int exynos_spi_close(struct exynos_spi *regs); -/* Serve as CBFS media source */ -int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, - void *buffer_address, - size_t buffer_size); +void exynos_init_spi_boot_device(void); +const struct region_device *exynos_spi_boot_device(void); #endif diff --git a/src/soc/samsung/exynos5250/spi.c b/src/soc/samsung/exynos5250/spi.c index 4f32487623..adf31e4e78 100644 --- a/src/soc/samsung/exynos5250/spi.c +++ b/src/soc/samsung/exynos5250/spi.c @@ -20,11 +20,13 @@ #include <arch/io.h> #include <assert.h> +#include <boot_device.h> #include <console/console.h> #include <soc/clk.h> #include <soc/gpio.h> #include <soc/spi.h> #include <stdlib.h> +#include <symbols.h> #if defined(CONFIG_DEBUG_SPI) && CONFIG_DEBUG_SPI # define DEBUG_SPI(x,...) printk(BIOS_DEBUG, "EXYNOS_SPI: " x) @@ -144,70 +146,47 @@ int exynos_spi_close(struct exynos_spi *regs) return 0; } -// SPI as CBFS media. -struct exynos_spi_media { - struct exynos_spi *regs; - struct cbfs_simple_buffer buffer; -}; - -static int exynos_spi_cbfs_open(struct cbfs_media *media) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_open\n"); - return exynos_spi_open(spi->regs); -} - -static int exynos_spi_cbfs_close(struct cbfs_media *media) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_close\n"); - return exynos_spi_close(spi->regs); -} +static struct exynos_spi *boot_slave_regs; -static size_t exynos_spi_cbfs_read(struct cbfs_media *media, void *dest, - size_t offset, size_t count) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; +static ssize_t exynos_spi_readat(const struct region_device *rdev, void *dest, + size_t offset, size_t count) +{ int bytes; DEBUG_SPI("exynos_spi_cbfs_read(%u)\n", count); - bytes = exynos_spi_read(spi->regs, dest, count, offset); - // Flush and re-open the device. - exynos_spi_close(spi->regs); - exynos_spi_open(spi->regs); + exynos_spi_open(boot_slave_regs); + bytes = exynos_spi_read(boot_slave_regs, dest, count, offset); + exynos_spi_close(boot_slave_regs); return bytes; } -static void *exynos_spi_cbfs_map(struct cbfs_media *media, size_t offset, - size_t count) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; +static void *exynos_spi_map(const struct region_device *rdev, + size_t offset, size_t count) +{ DEBUG_SPI("exynos_spi_cbfs_map\n"); - // See exynos_spi_rx_tx for I/O alignment limitation. + // exynos: spi_rx_tx may work in 4 byte-width-transmission mode and + // requires buffer memory address to be aligned. if (count % 4) count += 4 - (count % 4); - return cbfs_simple_buffer_map(&spi->buffer, media, offset, count); + return mmap_helper_rdev_mmap(rdev, offset, count); } -static void *exynos_spi_cbfs_unmap(struct cbfs_media *media, - const void *address) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_unmap\n"); - return cbfs_simple_buffer_unmap(&spi->buffer, address); -} +static const struct region_device_ops exynos_spi_ops = { + .mmap = exynos_spi_map, + .munmap = mmap_helper_rdev_munmap, + .readat = exynos_spi_readat, +}; -int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, - void *buffer_address, - size_t buffer_size) { - // TODO Replace static variable to support multiple streams. - static struct exynos_spi_media context; - DEBUG_SPI("initialize_exynos_spi_cbfs_media\n"); - - context.regs = (void*)EXYNOS5_SPI1_BASE; - context.buffer.allocated = context.buffer.last_allocate = 0; - context.buffer.buffer = buffer_address; - context.buffer.size = buffer_size; - media->context = (void*)&context; - media->open = exynos_spi_cbfs_open; - media->close = exynos_spi_cbfs_close; - media->read = exynos_spi_cbfs_read; - media->map = exynos_spi_cbfs_map; - media->unmap = exynos_spi_cbfs_unmap; +static struct mmap_helper_region_device mdev = + MMAP_HELPER_REGION_INIT(&exynos_spi_ops, 0, CONFIG_ROM_SIZE); - return 0; +void exynos_init_spi_boot_device(void) +{ + boot_slave_regs = (void *)EXYNOS5_SPI1_BASE; + + mmap_helper_device_init(&mdev, _cbfs_cache, _cbfs_cache_size); +} + +const struct region_device *exynos_spi_boot_device(void) +{ + return &mdev.rdev; } diff --git a/src/soc/samsung/exynos5420/alternate_cbfs.c b/src/soc/samsung/exynos5420/alternate_cbfs.c index b30ed6bff7..9bba748a84 100644 --- a/src/soc/samsung/exynos5420/alternate_cbfs.c +++ b/src/soc/samsung/exynos5420/alternate_cbfs.c @@ -20,6 +20,7 @@ #include <arch/cache.h> #include <assert.h> +#include <boot_device.h> #include <cbfs.h> /* This driver serves as a CBFS media source. */ #include <console/console.h> #include <soc/alternate_cbfs.h> @@ -46,7 +47,7 @@ * rest of the firmware's lifetime and all subsequent stages (which will not * have __PRE_RAM__ defined) can just directly reference it there. */ -static int usb_cbfs_open(struct cbfs_media *media) +static int usb_cbfs_open(void) { #ifdef __PRE_RAM__ static int first_run = 1; @@ -84,7 +85,7 @@ static int usb_cbfs_open(struct cbfs_media *media) * this seems like a safer approach. It also makes it easy to pass our image * down to payloads. */ -static int sdmmc_cbfs_open(struct cbfs_media *media) +static int sdmmc_cbfs_open(void) { #ifdef __PRE_RAM__ /* @@ -118,66 +119,109 @@ static int sdmmc_cbfs_open(struct cbfs_media *media) return 0; } -static int alternate_cbfs_close(struct cbfs_media *media) { return 0; } +static int exynos_cbfs_open(struct cbfs_media *media) { + return 0; +} + +static int exynos_cbfs_close(struct cbfs_media *media) { + return 0; +} + +static size_t exynos_cbfs_read(struct cbfs_media *media, void *dest, + size_t offset, size_t count) { + const struct region_device *boot_dev; + + boot_dev = media->context; + + if (rdev_readat(boot_dev, dest, offset, count) < 0) + return 0; -static size_t alternate_cbfs_read(struct cbfs_media *media, void *dest, - size_t offset, size_t count) -{ - ASSERT(offset + count < _cbfs_cache_size); - memcpy(dest, _cbfs_cache + offset, count); return count; } -static void *alternate_cbfs_map(struct cbfs_media *media, size_t offset, - size_t count) -{ - ASSERT(offset + count < _cbfs_cache_size); - return _cbfs_cache + offset; +static void *exynos_cbfs_map(struct cbfs_media *media, size_t offset, + size_t count) { + const struct region_device *boot_dev; + void *ptr; + + boot_dev = media->context; + + ptr = rdev_mmap(boot_dev, offset, count); + + if (ptr == NULL) + return (void *)-1; + + return ptr; } -static void *alternate_cbfs_unmap(struct cbfs_media *media, - const void *buffer) { return 0; } +static void *exynos_cbfs_unmap(struct cbfs_media *media, + const void *address) { + const struct region_device *boot_dev; -static int initialize_exynos_sdmmc_cbfs_media(struct cbfs_media *media) -{ - printk(BIOS_DEBUG, "Using Exynos alternate boot mode SDMMC\n"); + boot_dev = media->context; - media->open = sdmmc_cbfs_open; - media->close = alternate_cbfs_close; - media->read = alternate_cbfs_read; - media->map = alternate_cbfs_map; - media->unmap = alternate_cbfs_unmap; + rdev_munmap(boot_dev, (void *)address); - return 0; + return NULL; } -static int initialize_exynos_usb_cbfs_media(struct cbfs_media *media) +int init_default_cbfs_media(struct cbfs_media *media) { - printk(BIOS_DEBUG, "Using Exynos alternate boot mode USB A-A\n"); + boot_device_init(); - media->open = usb_cbfs_open; - media->close = alternate_cbfs_close; - media->read = alternate_cbfs_read; - media->map = alternate_cbfs_map; - media->unmap = alternate_cbfs_unmap; + media->context = (void *)boot_device_ro(); + + if (media->context == NULL) + return -1; + + media->open = exynos_cbfs_open; + media->close = exynos_cbfs_close; + media->read = exynos_cbfs_read; + media->map = exynos_cbfs_map; + media->unmap = exynos_cbfs_unmap; return 0; } -int init_default_cbfs_media(struct cbfs_media *media) +static struct mem_region_device alternate_rdev = MEM_REGION_DEV_INIT(NULL, 0); + +const struct region_device *boot_device_ro(void) { if (*iram_secondary_base == SECONDARY_BASE_BOOT_USB) - return initialize_exynos_usb_cbfs_media(media); + return &alternate_rdev.rdev; + + switch (exynos_power->om_stat & OM_STAT_MASK) { + case OM_STAT_SDMMC: + return &alternate_rdev.rdev; + case OM_STAT_SPI: + return exynos_spi_boot_device(); + default: + printk(BIOS_EMERG, "Exynos OM_STAT value 0x%x not supported!\n", + exynos_power->om_stat); + return NULL; + } +} + +void boot_device_init(void) +{ + mem_region_device_init(&alternate_rdev, _cbfs_cache, _cbfs_cache_size); + + if (*iram_secondary_base == SECONDARY_BASE_BOOT_USB) { + printk(BIOS_DEBUG, "Using Exynos alternate boot mode USB A-A\n"); + usb_cbfs_open(); + return; + } switch (exynos_power->om_stat & OM_STAT_MASK) { case OM_STAT_SDMMC: - return initialize_exynos_sdmmc_cbfs_media(media); + printk(BIOS_DEBUG, "Using Exynos alternate boot mode SDMMC\n"); + sdmmc_cbfs_open(); + break; case OM_STAT_SPI: - return initialize_exynos_spi_cbfs_media(media, - _cbfs_cache, _cbfs_cache_size); + exynos_init_spi_boot_device(); + break; default: printk(BIOS_EMERG, "Exynos OM_STAT value 0x%x not supported!\n", exynos_power->om_stat); - return 1; } } diff --git a/src/soc/samsung/exynos5420/include/soc/spi.h b/src/soc/samsung/exynos5420/include/soc/spi.h index cf778003be..77d5ffc8c7 100644 --- a/src/soc/samsung/exynos5420/include/soc/spi.h +++ b/src/soc/samsung/exynos5420/include/soc/spi.h @@ -20,8 +20,7 @@ #ifndef CPU_SAMSUNG_EXYNOS5420_SPI_H #define CPU_SAMSUNG_EXYNOS5420_SPI_H -/* This driver serves as a CBFS media source. */ -#include <cbfs.h> +#include <boot_device.h> /* SPI peripheral register map; padded to 64KB */ struct exynos_spi { @@ -91,8 +90,6 @@ check_member(exynos_spi, fb_clk, 0x2c); #define SPI_RX_BYTE_SWAP (1 << 6) #define SPI_RX_HWORD_SWAP (1 << 7) -/* Serve as CBFS media source */ -int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, - void *buffer_address, - size_t buffer_size); +void exynos_init_spi_boot_device(void); +const struct region_device *exynos_spi_boot_device(void); #endif diff --git a/src/soc/samsung/exynos5420/spi.c b/src/soc/samsung/exynos5420/spi.c index 5f298036ef..2edc3374dd 100644 --- a/src/soc/samsung/exynos5420/spi.c +++ b/src/soc/samsung/exynos5420/spi.c @@ -26,6 +26,7 @@ #include <spi_flash.h> #include <stdlib.h> #include <string.h> +#include <symbols.h> #define EXYNOS_SPI_MAX_TRANSFER_BYTES (65535) @@ -242,76 +243,43 @@ static int exynos_spi_read(struct spi_slave *slave, void *dest, uint32_t len, return len; } -// SPI as CBFS media. -struct exynos_spi_media { - struct spi_slave *slave; - struct cbfs_simple_buffer buffer; -}; - -static int exynos_spi_cbfs_open(struct cbfs_media *media) -{ - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_open\n"); - return spi_claim_bus(spi->slave); -} - -static int exynos_spi_cbfs_close(struct cbfs_media *media) -{ - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_close\n"); - spi_release_bus(spi->slave); - return 0; -} +static struct exynos_spi_slave *boot_slave; -static size_t exynos_spi_cbfs_read(struct cbfs_media *media, void *dest, - size_t offset, size_t count) +static ssize_t exynos_spi_readat(const struct region_device *rdev, void *dest, + size_t offset, size_t count) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - int bytes; DEBUG_SPI("exynos_spi_cbfs_read(%u)\n", count); - bytes = exynos_spi_read(spi->slave, dest, count, offset); - return bytes; + return exynos_spi_read(&boot_slave->slave, dest, count, offset); } -static void *exynos_spi_cbfs_map(struct cbfs_media *media, size_t offset, - size_t count) +static void *exynos_spi_map(const struct region_device *rdev, + size_t offset, size_t count) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; DEBUG_SPI("exynos_spi_cbfs_map\n"); // exynos: spi_rx_tx may work in 4 byte-width-transmission mode and // requires buffer memory address to be aligned. if (count % 4) count += 4 - (count % 4); - return cbfs_simple_buffer_map(&spi->buffer, media, offset, count); + return mmap_helper_rdev_mmap(rdev, offset, count); } -static void *exynos_spi_cbfs_unmap(struct cbfs_media *media, - const void *address) +static const struct region_device_ops exynos_spi_ops = { + .mmap = exynos_spi_map, + .munmap = mmap_helper_rdev_munmap, + .readat = exynos_spi_readat, +}; + +static struct mmap_helper_region_device mdev = + MMAP_HELPER_REGION_INIT(&exynos_spi_ops, 0, CONFIG_ROM_SIZE); + +void exynos_init_spi_boot_device(void) { - struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; - DEBUG_SPI("exynos_spi_cbfs_unmap\n"); - return cbfs_simple_buffer_unmap(&spi->buffer, address); + boot_slave = &exynos_spi_slaves[1]; + + mmap_helper_device_init(&mdev, _cbfs_cache, _cbfs_cache_size); } -int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, - void *buffer_address, - size_t buffer_size) +const struct region_device *exynos_spi_boot_device(void) { - // TODO Replace static variable to support multiple streams. - static struct exynos_spi_media context; - static struct exynos_spi_slave *eslave = &exynos_spi_slaves[1]; - DEBUG_SPI("initialize_exynos_spi_cbfs_media\n"); - - context.slave = &eslave->slave; - context.buffer.allocated = context.buffer.last_allocate = 0; - context.buffer.buffer = buffer_address; - context.buffer.size = buffer_size; - media->context = (void*)&context; - media->open = exynos_spi_cbfs_open; - media->close = exynos_spi_cbfs_close; - media->read = exynos_spi_cbfs_read; - media->map = exynos_spi_cbfs_map; - media->unmap = exynos_spi_cbfs_unmap; - - return 0; + return &mdev.rdev; } |