| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <linux/bitops.h> |
| #include <linux/err.h> |
| #include <linux/sizes.h> |
| #include <linux/types.h> |
| |
| #include "kvm/kvm.h" |
| #include "kvm/kvm-arch.h" |
| #include "kvm/kvm-cpu.h" |
| #include "kvm/devices.h" |
| #include "kvm/fdt.h" |
| #include "kvm/mutex.h" |
| #include "kvm/util.h" |
| |
| /* |
| * The EDK2 driver hardcodes two 16-bit chips on a 32-bit bus. |
| * This code supports one or two chips (enforced below). |
| */ |
| #define CFI_NR_FLASH_CHIPS 2 |
| |
| /* We always emulate a 32 bit bus width. */ |
| #define CFI_BUS_WIDTH 4 |
| |
| /* The *effective* size of an erase block (over all chips) */ |
| #define FLASH_BLOCK_SIZE SZ_64K |
| #define FLASH_BLOCK_SIZE_PER_CHIP \ |
| (FLASH_BLOCK_SIZE / CFI_NR_FLASH_CHIPS) |
| |
| #define PROGRAM_BUFF_SIZE_BITS 7 |
| #define PROGRAM_BUFF_SIZE (1U << PROGRAM_BUFF_SIZE_BITS) |
| #define PROGRAM_BUFF_SIZE_BITS_PER_CHIP \ |
| (PROGRAM_BUFF_SIZE_BITS + 1 - CFI_NR_FLASH_CHIPS) |
| |
| /* CFI commands */ |
| #define CFI_CMD_LOCK_BLOCK 0x01 |
| #define CFI_CMD_ALTERNATE_WORD_PROGRAM 0x10 |
| #define CFI_CMD_ERASE_BLOCK_SETUP 0x20 |
| #define CFI_CMD_WORD_PROGRAM 0x40 |
| #define CFI_CMD_CLEAR_STATUS_REG 0x50 |
| #define CFI_CMD_LOCK_BLOCK_SETUP 0x60 |
| #define CFI_CMD_READ_STATUS_REG 0x70 |
| #define CFI_CMD_READ_JEDEC_DEVID 0x90 |
| #define CFI_CMD_READ_CFI_QUERY 0x98 |
| #define CFI_CMD_CONFIRM 0xd0 |
| #define CFI_CMD_BUFFERED_PROGRAM_SETUP 0xe8 |
| #define CFI_CMD_READ_ARRAY 0xff |
| |
| #define CFI_STATUS_PROTECT_BIT 0x02 |
| #define CFI_STATUS_PROGRAM_LOCK_BIT 0x10 |
| #define CFI_STATUS_ERASE_CLEAR_LOCK_BIT 0x20 |
| #define CFI_STATUS_LOCK_ERROR CFI_STATUS_PROGRAM_LOCK_BIT | \ |
| CFI_STATUS_PROTECT_BIT |
| #define CFI_STATUS_ERASE_ERROR CFI_STATUS_ERASE_CLEAR_LOCK_BIT | \ |
| CFI_STATUS_PROGRAM_LOCK_BIT |
| #define CFI_STATUS_READY 0x80 |
| |
| /* |
| * CFI query table contents, as far as it is constant. |
| * The dynamic information (size, etc.) will be generated on the fly. |
| */ |
| #define CFI_GEOM_OFFSET 0x27 |
| static const u8 cfi_query_table[] = { |
| /* CFI query identification string */ |
| [0x10] = 'Q', 'R', 'Y', /* ID string */ |
| 0x01, 0x00, /* primary command set: Intel/Sharp extended */ |
| 0x31, 0x00, /* address of primary extended query table */ |
| 0x00, 0x00, /* alternative command set: unused */ |
| 0x00, 0x00, /* address of alternative extended query table*/ |
| /* system interface information */ |
| [0x1b] = 0x45, /* minimum Vcc voltage: 4.5V */ |
| 0x55, /* maximum Vcc voltage: 5.5V */ |
| 0x00, /* minimum Vpp voltage: 0.0V (unused) */ |
| 0x00, /* maximum Vpp voltage: 0.0V *(unused) */ |
| 0x01, /* timeout for single word program: 2 us */ |
| 0x01, /* timeout for multi-byte program: 2 us */ |
| 0x01, /* timeout for block erase: 2 ms */ |
| 0x00, /* timeout for full chip erase: not supported */ |
| 0x00, /* max timeout for single word program: 1x */ |
| 0x00, /* max timeout for mulit-byte program: 1x */ |
| 0x00, /* max timeout for block erase: 1x */ |
| 0x00, /* max timeout for chip erase: not supported */ |
| /* flash geometry information */ |
| [0x27] = 0x00, /* size in power-of-2 bytes, filled later */ |
| 0x05, 0x00, /* interface description: 32 and 16 bits */ |
| PROGRAM_BUFF_SIZE_BITS_PER_CHIP, 0x00, |
| /* number of bytes in write buffer */ |
| 0x01, /* one erase block region */ |
| 0x00, 0x00, 0x00, 0x00, /* number and size of erase blocks, generated */ |
| /* Intel primary algorithm extended query table */ |
| [0x31] = 'P', 'R', 'I', |
| '1', '0', /* version 1.0 */ |
| 0xa0, 0x00, 0x00, 0x00, /* optional features: instant lock & pm-read */ |
| 0x00, /* no functions after suspend */ |
| 0x01, 0x00, /* only lock bit supported */ |
| 0x50, /* best Vcc value: 5.0V */ |
| 0x00, /* best Vpp value: 0.0V (unused) */ |
| 0x01, /* number of protection register fields */ |
| 0x00, 0x00, 0x00, 0x00, /* protection field 1 description */ |
| }; |
| |
| /* |
| * Those states represent a subset of the CFI flash state machine. |
| */ |
| enum cfi_flash_state { |
| READY, |
| LOCK_BLOCK_SETUP, |
| WORD_PROGRAM, |
| BUFFERED_PROGRAM_SETUP, |
| BUFFER_WRITE, |
| ERASE_BLOCK_SETUP, |
| }; |
| |
| /* |
| * The device can be in several **Read** modes. |
| * We don't implement the asynchronous burst mode. |
| */ |
| enum cfi_read_mode { |
| READ_ARRAY, |
| READ_STATUS_REG, |
| READ_JEDEC_DEVID, |
| READ_CFI_QUERY, |
| }; |
| |
| struct cfi_flash_device { |
| struct device_header dev_hdr; |
| /* Protects the CFI state machine variables in this data structure. */ |
| struct mutex mutex; |
| u64 base_addr; |
| u32 size; |
| |
| void *flash_memory; |
| u8 program_buffer[PROGRAM_BUFF_SIZE]; |
| unsigned long *lock_bm; |
| u64 block_address; |
| unsigned int buff_written; |
| unsigned int buffer_length; |
| |
| enum cfi_flash_state state; |
| enum cfi_read_mode read_mode; |
| u8 sr; |
| bool is_mapped; |
| }; |
| |
| static int nr_erase_blocks(struct cfi_flash_device *sfdev) |
| { |
| return sfdev->size / FLASH_BLOCK_SIZE; |
| } |
| |
| /* |
| * CFI queries always deal with one byte of information, possibly mirrored |
| * to other bytes on the bus. This is dealt with in the callers. |
| * The address provided is the one for 8-bit addressing, and would need to |
| * be adjusted for wider accesses. |
| */ |
| static u8 read_cfi(struct cfi_flash_device *sfdev, u64 faddr) |
| { |
| if (faddr > sizeof(cfi_query_table)) { |
| pr_debug("CFI query read access beyond the end of table"); |
| return 0; |
| } |
| |
| /* Fixup dynamic information in the geometry part of the table. */ |
| switch (faddr) { |
| case 0x27: /* device size in bytes, power of two */ |
| return pow2_size(sfdev->size / CFI_NR_FLASH_CHIPS); |
| case 0x2d + 0: /* number of erase blocks, minus one */ |
| return (nr_erase_blocks(sfdev) - 1) & 0xff; |
| case 0x2d + 1: |
| return ((nr_erase_blocks(sfdev) - 1) >> 8) & 0xff; |
| case 0x2d + 2: /* erase block size, in units of 256 */ |
| return (FLASH_BLOCK_SIZE_PER_CHIP / 256) & 0xff; |
| case 0x2d + 3: |
| return ((FLASH_BLOCK_SIZE_PER_CHIP / 256) >> 8) & 0xff; |
| } |
| |
| return cfi_query_table[faddr]; |
| } |
| |
| static bool block_is_locked(struct cfi_flash_device *sfdev, u64 faddr) |
| { |
| int block_nr = faddr / FLASH_BLOCK_SIZE; |
| |
| return test_bit(block_nr, sfdev->lock_bm); |
| } |
| |
| #define DEV_ID_MASK 0x7ff |
| static u16 read_dev_id(struct cfi_flash_device *sfdev, u64 faddr) |
| { |
| switch ((faddr & DEV_ID_MASK) / CFI_BUS_WIDTH) { |
| case 0x0: /* vendor ID */ |
| return 0x0000; |
| case 0x1: /* device ID */ |
| return 0xffff; |
| case 0x2: |
| return block_is_locked(sfdev, faddr & ~DEV_ID_MASK); |
| default: /* Ignore the other entries. */ |
| return 0; |
| } |
| } |
| |
| static void lock_block(struct cfi_flash_device *sfdev, u64 faddr, bool lock) |
| { |
| int block_nr = faddr / FLASH_BLOCK_SIZE; |
| |
| if (lock) |
| set_bit(block_nr, sfdev->lock_bm); |
| else |
| clear_bit(block_nr, sfdev->lock_bm); |
| } |
| |
| static void word_program(struct cfi_flash_device *sfdev, |
| u64 faddr, void *data, int len) |
| { |
| if (block_is_locked(sfdev, faddr)) { |
| sfdev->sr |= CFI_STATUS_LOCK_ERROR; |
| return; |
| } |
| |
| memcpy(sfdev->flash_memory + faddr, data, len); |
| } |
| |
| /* Reset the program buffer state to prepare for follow-up writes. */ |
| static void buffer_setup(struct cfi_flash_device *sfdev) |
| { |
| memset(sfdev->program_buffer, 0, sizeof(sfdev->program_buffer)); |
| sfdev->block_address = ~0ULL; |
| sfdev->buff_written = 0; |
| } |
| |
| static bool buffer_write(struct cfi_flash_device *sfdev, |
| u64 faddr, void *buffer, int len) |
| { |
| unsigned int buff_addr; |
| |
| if (sfdev->buff_written >= sfdev->buffer_length) |
| return false; |
| |
| /* |
| * The first word written into the buffer after the setup command |
| * happens to be the base address for the buffer. |
| * All subsequent writes need to be within this address and this |
| * address plus the buffer size, so keep this value around. |
| */ |
| if (sfdev->block_address == ~0ULL) |
| sfdev->block_address = faddr; |
| |
| if (faddr < sfdev->block_address) |
| return false; |
| buff_addr = faddr - sfdev->block_address; |
| if (buff_addr >= PROGRAM_BUFF_SIZE) |
| return false; |
| |
| memcpy(sfdev->program_buffer + buff_addr, buffer, len); |
| sfdev->buff_written += len; |
| |
| return true; |
| } |
| |
| static void buffer_confirm(struct cfi_flash_device *sfdev) |
| { |
| if (block_is_locked(sfdev, sfdev->block_address)) { |
| sfdev->sr |= CFI_STATUS_LOCK_ERROR; |
| return; |
| } |
| memcpy(sfdev->flash_memory + sfdev->block_address, |
| sfdev->program_buffer, sfdev->buff_written); |
| } |
| |
| static void block_erase_confirm(struct cfi_flash_device *sfdev, u64 faddr) |
| { |
| if (block_is_locked(sfdev, faddr)) { |
| sfdev->sr |= CFI_STATUS_LOCK_ERROR; |
| return; |
| } |
| |
| memset(sfdev->flash_memory + faddr, 0xff, FLASH_BLOCK_SIZE); |
| } |
| |
| static void cfi_flash_read(struct cfi_flash_device *sfdev, |
| u64 faddr, u8 *data, u32 len) |
| { |
| u16 cfi_value = 0; |
| |
| switch (sfdev->read_mode) { |
| case READ_ARRAY: |
| /* just copy the requested bytes from the array */ |
| memcpy(data, sfdev->flash_memory + faddr, len); |
| return; |
| case READ_STATUS_REG: |
| cfi_value = sfdev->sr; |
| break; |
| case READ_JEDEC_DEVID: |
| cfi_value = read_dev_id(sfdev, faddr); |
| break; |
| case READ_CFI_QUERY: |
| cfi_value = read_cfi(sfdev, faddr / CFI_BUS_WIDTH); |
| break; |
| } |
| switch (len) { |
| case 1: |
| *data = cfi_value; |
| break; |
| case 8: memset(data + 4, 0, 4); |
| /* fall-through */ |
| case 4: |
| if (CFI_NR_FLASH_CHIPS == 2) |
| memcpy(data + 2, &cfi_value, 2); |
| else |
| memset(data + 2, 0, 2); |
| /* fall-through */ |
| case 2: |
| memcpy(data, &cfi_value, 2); |
| break; |
| default: |
| pr_debug("CFI flash: illegal access length %d for read mode %d", |
| len, sfdev->read_mode); |
| break; |
| } |
| } |
| |
| /* |
| * Any writes happening in "READY" state don't actually write to the memory, |
| * but are really treated as commands to advance the state machine and select |
| * the next action. |
| * Change the state and modes according to the value written. The address |
| * that value is written to does not matter and is ignored. |
| */ |
| static void cfi_flash_write_ready(struct cfi_flash_device *sfdev, u8 command) |
| { |
| switch (command) { |
| case CFI_CMD_READ_JEDEC_DEVID: |
| sfdev->read_mode = READ_JEDEC_DEVID; |
| break; |
| case CFI_CMD_READ_STATUS_REG: |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| case CFI_CMD_READ_CFI_QUERY: |
| sfdev->read_mode = READ_CFI_QUERY; |
| break; |
| case CFI_CMD_CLEAR_STATUS_REG: |
| sfdev->sr = CFI_STATUS_READY; |
| break; |
| case CFI_CMD_WORD_PROGRAM: |
| case CFI_CMD_ALTERNATE_WORD_PROGRAM: |
| sfdev->state = WORD_PROGRAM; |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| case CFI_CMD_LOCK_BLOCK_SETUP: |
| sfdev->state = LOCK_BLOCK_SETUP; |
| break; |
| case CFI_CMD_ERASE_BLOCK_SETUP: |
| sfdev->state = ERASE_BLOCK_SETUP; |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| case CFI_CMD_BUFFERED_PROGRAM_SETUP: |
| buffer_setup(sfdev); |
| sfdev->state = BUFFERED_PROGRAM_SETUP; |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| case CFI_CMD_CONFIRM: |
| pr_debug("CFI flash: unexpected confirm command 0xd0"); |
| break; |
| default: |
| pr_debug("CFI flash: unknown command 0x%x", command); |
| /* fall-through */ |
| case CFI_CMD_READ_ARRAY: |
| sfdev->read_mode = READ_ARRAY; |
| break; |
| } |
| } |
| |
| static void cfi_flash_write(struct cfi_flash_device *sfdev, u16 command, |
| u64 faddr, u8 *data, u32 len) |
| { |
| switch (sfdev->state) { |
| case READY: |
| cfi_flash_write_ready(sfdev, command & 0xff); |
| return; |
| case LOCK_BLOCK_SETUP: |
| switch (command & 0xff) { |
| case CFI_CMD_LOCK_BLOCK: |
| lock_block(sfdev, faddr, true); |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| case CFI_CMD_CONFIRM: |
| lock_block(sfdev, faddr, false); |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| default: |
| sfdev->sr |= CFI_STATUS_ERASE_ERROR; |
| break; |
| } |
| sfdev->state = READY; |
| break; |
| |
| case WORD_PROGRAM: |
| word_program(sfdev, faddr, data, len); |
| sfdev->read_mode = READ_STATUS_REG; |
| sfdev->state = READY; |
| break; |
| |
| case BUFFER_WRITE: |
| if (buffer_write(sfdev, faddr, data, len)) |
| break; |
| |
| if ((command & 0xff) == CFI_CMD_CONFIRM) { |
| buffer_confirm(sfdev); |
| sfdev->read_mode = READ_STATUS_REG; |
| } else { |
| pr_debug("CFI flash: BUFFER_WRITE: expected CONFIRM(0xd0), got 0x%x @ 0x%llx", |
| command, faddr); |
| sfdev->sr |= CFI_STATUS_PROGRAM_LOCK_BIT; |
| } |
| sfdev->state = READY; |
| break; |
| |
| case BUFFERED_PROGRAM_SETUP: |
| sfdev->buffer_length = (command + 1) * CFI_BUS_WIDTH; |
| if (sfdev->buffer_length > PROGRAM_BUFF_SIZE) |
| sfdev->buffer_length = PROGRAM_BUFF_SIZE; |
| sfdev->state = BUFFER_WRITE; |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| |
| case ERASE_BLOCK_SETUP: |
| if ((command & 0xff) == CFI_CMD_CONFIRM) |
| block_erase_confirm(sfdev, faddr); |
| else |
| sfdev->sr |= CFI_STATUS_ERASE_ERROR; |
| |
| sfdev->state = READY; |
| sfdev->read_mode = READ_STATUS_REG; |
| break; |
| default: |
| pr_debug("CFI flash: unexpected/unknown command 0x%x", command); |
| break; |
| } |
| } |
| |
| /* |
| * If we are in ARRAY_READ mode, we can map the flash array directly |
| * into the guest, just as read-only. This greatly improves read |
| * performance, and avoids problems with exits due to accesses from |
| * load instructions without syndrome information (on ARM). |
| * Also it could allow code to be executed XIP in there. |
| */ |
| static int map_flash_memory(struct kvm *kvm, struct cfi_flash_device *sfdev) |
| { |
| int ret; |
| |
| ret = kvm__register_mem(kvm, sfdev->base_addr, sfdev->size, |
| sfdev->flash_memory, |
| KVM_MEM_TYPE_RAM | KVM_MEM_TYPE_READONLY); |
| if (!ret) |
| sfdev->is_mapped = true; |
| |
| return ret; |
| } |
| |
| /* |
| * Any write access changing the read mode would need to bring us back to |
| * "trap everything", as the CFI query read need proper handholding. |
| */ |
| static int unmap_flash_memory(struct kvm *kvm, struct cfi_flash_device *sfdev) |
| { |
| int ret; |
| |
| ret = kvm__destroy_mem(kvm, sfdev->base_addr, sfdev->size, |
| sfdev->flash_memory); |
| |
| if (!ret) |
| sfdev->is_mapped = false; |
| |
| return ret; |
| } |
| |
| static void cfi_flash_mmio(struct kvm_cpu *vcpu, |
| u64 addr, u8 *data, u32 len, u8 is_write, |
| void *context) |
| { |
| struct cfi_flash_device *sfdev = context; |
| u64 faddr = addr - sfdev->base_addr; |
| u32 value; |
| |
| if (!is_write) { |
| mutex_lock(&sfdev->mutex); |
| |
| cfi_flash_read(sfdev, faddr, data, len); |
| |
| mutex_unlock(&sfdev->mutex); |
| |
| return; |
| } |
| |
| if (len > 4) { |
| pr_info("CFI flash: MMIO %d-bit write access not supported", |
| len * 8); |
| return; |
| } |
| |
| memcpy(&value, data, len); |
| |
| mutex_lock(&sfdev->mutex); |
| |
| cfi_flash_write(sfdev, value & 0xffff, faddr, data, len); |
| |
| /* Adjust our mapping status accordingly. */ |
| if (!sfdev->is_mapped && sfdev->read_mode == READ_ARRAY) |
| map_flash_memory(vcpu->kvm, sfdev); |
| else if (sfdev->is_mapped && sfdev->read_mode != READ_ARRAY) |
| unmap_flash_memory(vcpu->kvm, sfdev); |
| |
| mutex_unlock(&sfdev->mutex); |
| } |
| |
| #ifdef CONFIG_HAS_LIBFDT |
| static void generate_cfi_flash_fdt_node(void *fdt, |
| struct device_header *dev_hdr, |
| void (*generate_irq_prop)(void *fdt, |
| u8 irq, |
| enum irq_type)) |
| { |
| struct cfi_flash_device *sfdev; |
| u64 reg_prop[2]; |
| |
| sfdev = container_of(dev_hdr, struct cfi_flash_device, dev_hdr); |
| reg_prop[0] = cpu_to_fdt64(sfdev->base_addr); |
| reg_prop[1] = cpu_to_fdt64(sfdev->size); |
| |
| _FDT(fdt_begin_node(fdt, "flash")); |
| _FDT(fdt_property_cell(fdt, "bank-width", CFI_BUS_WIDTH)); |
| _FDT(fdt_property_cell(fdt, "#address-cells", 0x1)); |
| _FDT(fdt_property_cell(fdt, "#size-cells", 0x1)); |
| _FDT(fdt_property_string(fdt, "compatible", "cfi-flash")); |
| _FDT(fdt_property_string(fdt, "label", "System-firmware")); |
| _FDT(fdt_property(fdt, "reg", ®_prop, sizeof(reg_prop))); |
| _FDT(fdt_end_node(fdt)); |
| } |
| #else |
| #define generate_cfi_flash_fdt_node NULL |
| #endif |
| |
| static struct cfi_flash_device *create_flash_device_file(struct kvm *kvm, |
| const char *filename) |
| { |
| struct cfi_flash_device *sfdev; |
| struct stat statbuf; |
| unsigned int value; |
| int ret; |
| int fd; |
| |
| fd = open(filename, O_RDWR); |
| if (fd < 0) |
| return ERR_PTR(-errno); |
| |
| if (fstat(fd, &statbuf) < 0) { |
| ret = -errno; |
| goto out_close; |
| } |
| |
| sfdev = malloc(sizeof(struct cfi_flash_device)); |
| if (!sfdev) { |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| sfdev->size = statbuf.st_size; |
| /* Round down to nearest power-of-2 size value. */ |
| sfdev->size = 1U << (pow2_size(sfdev->size + 1) - 1); |
| if (sfdev->size > KVM_FLASH_MAX_SIZE) |
| sfdev->size = KVM_FLASH_MAX_SIZE; |
| if (sfdev->size < statbuf.st_size) { |
| pr_info("flash file size (%llu bytes) is not a power of two", |
| (unsigned long long)statbuf.st_size); |
| pr_info("only using first %u bytes", sfdev->size); |
| } |
| sfdev->flash_memory = mmap(NULL, sfdev->size, |
| PROT_READ | PROT_WRITE, MAP_SHARED, |
| fd, 0); |
| if (sfdev->flash_memory == MAP_FAILED) { |
| ret = -errno; |
| goto out_free; |
| } |
| sfdev->base_addr = KVM_FLASH_MMIO_BASE; |
| sfdev->state = READY; |
| sfdev->read_mode = READ_ARRAY; |
| sfdev->sr = CFI_STATUS_READY; |
| |
| map_flash_memory(kvm, sfdev); |
| |
| value = roundup(nr_erase_blocks(sfdev), BITS_PER_LONG) / 8; |
| sfdev->lock_bm = malloc(value); |
| memset(sfdev->lock_bm, 0, value); |
| |
| sfdev->dev_hdr.bus_type = DEVICE_BUS_MMIO; |
| sfdev->dev_hdr.data = generate_cfi_flash_fdt_node; |
| mutex_init(&sfdev->mutex); |
| ret = device__register(&sfdev->dev_hdr); |
| if (ret) |
| goto out_unmap; |
| |
| ret = kvm__register_mmio(kvm, |
| sfdev->base_addr, sfdev->size, |
| false, cfi_flash_mmio, sfdev); |
| if (ret) { |
| device__unregister(&sfdev->dev_hdr); |
| goto out_unmap; |
| } |
| |
| return sfdev; |
| |
| out_unmap: |
| munmap(sfdev->flash_memory, sfdev->size); |
| out_free: |
| free(sfdev); |
| out_close: |
| close(fd); |
| |
| return ERR_PTR(ret); |
| } |
| |
| static int cfi_flash__init(struct kvm *kvm) |
| { |
| struct cfi_flash_device *sfdev; |
| |
| BUILD_BUG_ON(CFI_NR_FLASH_CHIPS != 1 && CFI_NR_FLASH_CHIPS != 2); |
| |
| if (!kvm->cfg.flash_filename) |
| return 0; |
| |
| sfdev = create_flash_device_file(kvm, kvm->cfg.flash_filename); |
| if (IS_ERR(sfdev)) |
| return PTR_ERR(sfdev); |
| |
| return 0; |
| } |
| dev_init(cfi_flash__init); |