/* * Copyright (C) yajin 2008 * * This file is part of the virtualmips distribution. * See LICENSE file for terms of the license. * */ /* 1G bytes nand flash emulation. Samsung K9F8G08 1GB 1G bytes nand flash are stored in file nandflash8g.0-nandflash8g.8191 (8192 blocks). The flash file only be created when writing to block (copy on write). Please use tool/mknandflash to create init nand file of u-boot image. */ /* supported operation: READ Read for copy back Read ID Reset Page program Copy-back Block erase Random data input Random data output */ #include #include #include #include #include #include #include #include #include #include #include #include "device.h" #include "mips_memory.h" #include "dev_nand_flash_1g.h" m_uint8_t id_info[5] = { 0xec, 0xd3, 0x51, 0x95, 0x58 }; #define NAND_STATUS_READY 0x40 #define NAND_STATUS_WP 0x80 /*status ready & not write protected*/ /*I am always ready. haha. Better than real nand flash :)*/ m_uint8_t nand_status = NAND_STATUS_READY | NAND_STATUS_WP; #ifdef CPU_LOG #undef CPU_LOG #endif //#define DEBUG_FLASH_ACCESS #ifdef DEBUG_FLASH_ACCESS #define CPU_LOG(arg1) cpu_log arg1 #else #define CPU_LOG(arg1) #endif /*Create nand flash file. 1 block 1 file.*/ static unsigned char *create_nand_flash_file (m_uint32_t block_no) { char file_path[64]; char page[NAND_FLASH_1G_PAGE_SIZE]; int i, n; int fd; unsigned char *ret; /*create nand flash file when writing */ snprintf (file_path, sizeof (file_path), "%s/%s.%d", NAND_FLASH_1G_FILE_DIR, NAND_FLASH_1G_FILE_PREFIX, block_no); fd = open (file_path, O_RDWR | O_CREAT, S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); assert (fd >= 0); for (i = 0; i < NAND_FLASH_1G_PAGES_PER_BLOCK; i++) { memset (page, 0xff, NAND_FLASH_1G_PAGE_SIZE); n = write (fd, page, NAND_FLASH_1G_PAGE_SIZE); assert (n == NAND_FLASH_1G_PAGE_SIZE); } ret = memzone_map_file (fd, NAND_FLASH_1G_BLOCK_SIZE); assert (ret != NULL); return ret; } /*get the page pointer given row addr and block start address*/ unsigned char *get_nand_flash_page_ptr (m_uint32_t row_addr, unsigned char *block_start) { m_uint32_t block_no = row_addr >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; m_uint32_t page_no = row_addr & NAND_FLASH_1G_BLOCK_PAGE_MASK; assert (block_no < NAND_FLASH_1G_TOTAL_BLOCKS); assert (block_start != NULL); return (block_start + page_no * NAND_FLASH_1G_PAGE_SIZE); } static void nand_flash_erase_block (unsigned char *block_start) { memset (block_start, 0xff, NAND_FLASH_1G_BLOCK_SIZE); } /*write data to nand file (1 page)*/ static void write_nand_fiash_page_file (m_uint32_t row_addr, unsigned char *block_start, unsigned char *write_data) { unsigned char *page_ptr; int i; page_ptr = get_nand_flash_page_ptr (row_addr, block_start); /*we only copy different data into page */ for (i = 0; i < NAND_FLASH_1G_PAGE_SIZE; i++) { if (*(write_data + i) == 0XFF) continue; if ((*(page_ptr + i)) != (*(write_data + i))) { *(page_ptr + i) = *(write_data + i); } } } char *state_string[8] = { "STATE_INIT", "STATE_READ_START", "STATE_RANDOM_READ_START", "STATE_WRITE_START", "STATE_RANDOM_WRITE_START", "STATE_READ_PAGE_FOR_COPY_WRITE", "STATE_COPY_START", "STATE_ERASE_START", }; void *dev_nand_flash_1g_access (cpu_mips_t * cpu, struct vdevice *dev, m_uint32_t offset, u_int op_size, u_int op_type, m_uint32_t * data, m_uint8_t * has_set_value) { nand_flash_1g_data_t *d = dev->priv_data; m_uint32_t block_no; void *ret; /*COMMAND PORT */ if (offset == NAND_COMMPORT_OFFSET) { /*clear addr offset */ d->addr_offset = 0; switch (d->state) { case STATE_INIT: if (((*data) & 0xff) == 0x00) d->state = STATE_READ_START; else if (((*data) & 0xff) == 0x80) { memset (d->write_buffer, 0xff, NAND_FLASH_1G_PAGE_SIZE); d->state = STATE_WRITE_START; } else if (((*data) & 0xff) == 0x05) { assert (d->has_issue_30h == 1); d->state = STATE_RANDOM_READ_START; d->has_issue_30h = 0; } else if (((*data) & 0xff) == 0xFF) { /*reset */ d->state = STATE_INIT; } else if (((*data) & 0xff) == 0x90) { /*read ID */ d->data_port_ipr = id_info; d->state = STATE_INIT; d->read_offset = 0; } else if (((*data) & 0xff) == 0x60) { /*ERASE */ d->state = STATE_ERASE_START; /*erase only need row address. adjust addr_offset */ d->addr_offset = 2; } else if (((*data) & 0xff) == 0x70) { /*READ STATUS */ d->state = STATE_INIT; d->read_offset = 0; d->data_port_ipr = &nand_status; } else ASSERT (0, "*data %x\n", *data); break; case STATE_ERASE_START: if (((*data) & 0xff) == 0xd0) { //erase blcok block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) d->flash_map[block_no] = create_nand_flash_file (block_no); nand_flash_erase_block (d->flash_map[block_no]); d->state = STATE_INIT; } else assert (0); break; case STATE_READ_START: if (((*data) & 0xff) == 0x30) { d->has_issue_30h = 1; d->state = STATE_INIT; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) { CPU_LOG ((cpu, "", "block_no %x is null. redirect to fake page.", block_no)); d->data_port_ipr = get_nand_flash_page_ptr (d->row_addr, d->fake_block); } else d->data_port_ipr = get_nand_flash_page_ptr (d->row_addr, d->flash_map[block_no]); d->read_offset = d->col_addr; CPU_LOG ((cpu, "", "d->read_offset %x d->col_addr %x.", d->read_offset, d->col_addr)); } else if (((*data) & 0xff) == 0x35) { d->state = STATE_READ_PAGE_FOR_COPY_WRITE; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; memset (d->write_buffer, 0xff, NAND_FLASH_1G_PAGE_SIZE); } else if (((*data) & 0xff) == 0xFF) { /*reset */ d->state = STATE_INIT; } else assert (0); break; case STATE_RANDOM_READ_START: if (((*data) & 0xff) == 0xe0) { d->state = STATE_INIT; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) { CPU_LOG ((cpu, "", "block_no %x is null. redirect to fake page.", block_no)); d->data_port_ipr = get_nand_flash_page_ptr (d->row_addr, d->fake_block); } else d->data_port_ipr = get_nand_flash_page_ptr (d->row_addr, d->flash_map[block_no]); d->read_offset = d->col_addr; } else if (((*data) & 0xff) == 0x05) { d->state = STATE_RANDOM_READ_START; } else assert (0); break; case STATE_WRITE_START: if (((*data) & 0xff) == 0x10) { d->state = STATE_INIT; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) d->flash_map[block_no] = create_nand_flash_file (block_no); write_nand_fiash_page_file (d->row_addr, d->flash_map[block_no], d->write_buffer); d->write_offset = 0; } else if (((*data) & 0xff) == 0x85) { d->write_offset = 0; d->state = STATE_RANDOM_WRITE_START; } else assert (0); break; case STATE_RANDOM_WRITE_START: if (((*data) & 0xff) == 0x10) { d->state = STATE_INIT; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) d->flash_map[block_no] = create_nand_flash_file (block_no); write_nand_fiash_page_file (d->row_addr, d->flash_map[block_no], d->write_buffer); d->write_offset = 0; } else if (((*data) & 0xff) == 0x85) { d->write_offset = 0; d->state = STATE_RANDOM_WRITE_START; } else assert (0); break; case STATE_READ_PAGE_FOR_COPY_WRITE: if (((*data) & 0xff) == 0x85) { d->write_offset = 0; d->state = STATE_COPY_START; } else assert (0); break; case STATE_COPY_START: if (((*data) & 0xff) == 0x10) { d->state = STATE_INIT; block_no = (d->row_addr) >> NAND_FLASH_1G_BLOCK_PAGE_OFFSET; if (d->flash_map[block_no] == NULL) d->flash_map[block_no] = create_nand_flash_file (block_no); write_nand_fiash_page_file (d->row_addr, d->flash_map[block_no], d->write_buffer); d->write_offset = 0; } else if (((*data) & 0xff) == 0x85) { d->write_offset = 0; d->state = STATE_RANDOM_WRITE_START; } else assert (0); break; default: assert (0); } CPU_LOG ((cpu, "", " state %s\n", state_string[d->state])); } else if (offset == NAND_DATAPORT_OFFSET) { *has_set_value = FALSE; if (op_type == MTS_READ) { /*data port */ CPU_LOG ((cpu, "", "pc %x data %x d->read_offset %x d->data_port_ipr %x \n", cpu->pc, *(d->data_port_ipr + d->read_offset), d->read_offset, d->data_port_ipr)); ret = (void *) (d->data_port_ipr + d->read_offset); d->read_offset++; return ret; } else if (op_type == MTS_WRITE) { ret = (void *) (d->write_buffer + d->col_addr + d->write_offset); d->write_offset++; return ret; } assert (0); } else if (offset == NAND_ADDRPORT_OFFSET) { CPU_LOG ((cpu, "", "ADDRESS pc %x d->addr_offset %x *data %x \n", cpu->pc, d->addr_offset, *data)); /*ADDRESS PORT */ assert (op_type == MTS_WRITE); *has_set_value = TRUE; switch (d->addr_offset) { case 0x0: d->col_addr = (*data) & 0xff; break; case 0x01: d->col_addr += ((*data) & 0xff) << 8; break; case 0x2: d->row_addr = (*data) & 0xff; break; case 0x03: d->row_addr += ((*data) & 0xff) << 8; break; case 0x04: d->row_addr += ((*data) & 0xff) << 16; break; default: assert (0); } CPU_LOG ((cpu, "", "col_addr %x row_addr %x\n", d->col_addr, d->row_addr)); d->addr_offset++; } *has_set_value = TRUE; return NULL; } static int load_nand_flash_file (nand_flash_1g_data_t * d) { int i, j = 0; struct dirent *ent = NULL; DIR *p_dir; char file_path[64]; char *file_name; char block_number[16]; int fd; //nand_flash_1g_data_t *d=*nand_flash; memset (d->flash_map, 0x0, NAND_FLASH_1G_TOTAL_BLOCKS * sizeof (d->flash_map[0])); p_dir = opendir (NAND_FLASH_1G_FILE_DIR); if (NULL == p_dir) { fprintf (stderr, "NAND FLASH: Can not open nand flash file directory \"%s\".\n", NAND_FLASH_1G_FILE_DIR); goto err_flash_map_create; } while (NULL != (ent = readdir (p_dir))) { //we only take care file if (ent->d_type == DT_DIR) continue; snprintf (file_path, sizeof (file_path), "%s/%s", NAND_FLASH_1G_FILE_DIR, ent->d_name); if (get_file_size (file_path) != NAND_FLASH_1G_BLOCK_SIZE) continue; file_name = strdup (ent->d_name); for (i = strlen (file_name) - 1; i >= 0; i--) { if (file_name[i] == '.') break; } if (i == -1) { //not a valid flash file continue; } file_name[i] = '\0'; if (strcmp (file_name, NAND_FLASH_1G_FILE_PREFIX) != 0) { continue; } free (file_name); file_name = strdup (ent->d_name); //get the block number strncpy (block_number, file_name + i + 1, strlen (file_name) - i - 1); block_number[strlen (file_name) - i - 1] = '\0'; i = atoi (block_number); fd = open (file_path, O_RDWR); if (fd < 0) goto err_open_flash_file; d->flash_map[i] = memzone_map_file (fd, NAND_FLASH_1G_BLOCK_SIZE); if (d->flash_map[i] == NULL) goto err_map_flash_file; close (fd); free (file_name); j++; } closedir (p_dir); printf ("\nloaded %d nand flash file from directory \"%s\". \n", j, NAND_FLASH_1G_FILE_DIR); return (0); err_map_flash_file: close (fd); err_open_flash_file: free (file_name); err_flash_map_create: return (-1); } void dev_nand_flash_1g_reset (cpu_mips_t * cpu, struct vdevice *dev) { nand_flash_1g_data_t *d = dev->priv_data; memset (d->fake_block, 0xff, NAND_FLASH_1G_BLOCK_SIZE); d->state = STATE_INIT; memset (d->write_buffer, 0xff, NAND_FLASH_1G_PAGE_SIZE); } int dev_nand_flash_1g_init (vm_instance_t * vm, char *name, m_pa_t phys_addr, m_uint32_t phys_len, nand_flash_1g_data_t ** nand_flash) { nand_flash_1g_data_t *d; /* allocate the private data structure */ if (!(d = malloc (sizeof (*d)))) { fprintf (stderr, "NAND FLASH: unable to create device.\n"); return (-1); } memset (d, 0, sizeof (*d)); /*load all flash data to d->flash_map */ if (load_nand_flash_file (d) == -1) return (-1); /*set fake_page * We only create nand flash file when writing to a blcok. * When reading from a block which has not been written, give it the fake_page. */ memset (d->fake_block, 0xff, NAND_FLASH_1G_BLOCK_SIZE); d->state = STATE_INIT; memset (d->write_buffer, 0xff, NAND_FLASH_1G_PAGE_SIZE); if (!(d->dev = dev_create (name))) goto err_dev_create; d->dev->priv_data = d; d->dev->handler = dev_nand_flash_1g_access; d->dev->reset_handler = dev_nand_flash_1g_reset; /*NAND COMMPORT AND DATA PORT ADDRESS */ d->dev->phys_addr = phys_addr; d->dev->phys_len = phys_len; d->dev->flags = VDEVICE_FLAG_NO_MTS_MMAP; /* Map this device to the VM */ vm_bind_device (vm, d->dev); *nand_flash = d; return (0); err_dev_create: free (d); return (-1); }