446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * mtd vendor storage
 | |
|  */
 | |
| 
 | |
| #include <linux/debugfs.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/fs.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux/miscdevice.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/mtd/mtd.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/soc/rockchip/rk_vendor_storage.h>
 | |
| #include <linux/uaccess.h>
 | |
| #include <linux/vmalloc.h>
 | |
| #include <misc/rkflash_vendor_storage.h>
 | |
| 
 | |
| #define MTD_VENDOR_PART_START		0
 | |
| #define MTD_VENDOR_PART_SIZE		FLASH_VENDOR_PART_SIZE
 | |
| #define MTD_VENDOR_NOR_BLOCK_SIZE	128
 | |
| #define MTD_VENDOR_PART_NUM		1
 | |
| #define MTD_VENDOR_TAG			VENDOR_HEAD_TAG
 | |
| 
 | |
| struct mtd_nand_info {
 | |
| 	u32 blk_offset;
 | |
| 	u32 page_offset;
 | |
| 	u32 version;
 | |
| 	u32 ops_size;
 | |
| };
 | |
| 
 | |
| #ifdef CONFIG_ROCKCHIP_VENDOR_STORAGE_UPDATE_LOADER
 | |
| #define READ_SECTOR_IO		_IOW('r', 0x04, unsigned int)
 | |
| #define WRITE_SECTOR_IO		_IOW('r', 0x05, unsigned int)
 | |
| #define END_WRITE_SECTOR_IO	_IOW('r', 0x52, unsigned int)
 | |
| #define GET_FLASH_INFO_IO	_IOW('r', 0x1A, unsigned int)
 | |
| #define GET_BAD_BLOCK_IO	_IOW('r', 0x03, unsigned int)
 | |
| #define GET_LOCK_FLAG_IO	_IOW('r', 0x53, unsigned int)
 | |
| #endif
 | |
| 
 | |
| static u8 *g_idb_buffer;
 | |
| static struct flash_vendor_info *g_vendor;
 | |
| static DEFINE_MUTEX(vendor_ops_mutex);
 | |
| static struct mtd_info *mtd;
 | |
| static u32 mtd_erase_size;
 | |
| static const char *vendor_mtd_name = "vnvm";
 | |
| static struct mtd_nand_info nand_info;
 | |
| static struct platform_device *g_pdev;
 | |
| 
 | |
| static int mtd_vendor_nand_write(void)
 | |
| {
 | |
| 	size_t bytes_write;
 | |
| 	int err, count = 0;
 | |
| 	struct erase_info ei;
 | |
| 
 | |
| re_write:
 | |
| 	if (nand_info.page_offset >= mtd_erase_size) {
 | |
| 		nand_info.blk_offset += mtd_erase_size;
 | |
| 		if (nand_info.blk_offset >= mtd->size)
 | |
| 			nand_info.blk_offset = 0;
 | |
| 		if (mtd_block_isbad(mtd, nand_info.blk_offset))
 | |
| 			goto re_write;
 | |
| 
 | |
| 		memset(&ei, 0, sizeof(struct erase_info));
 | |
| 		ei.addr = nand_info.blk_offset;
 | |
| 		ei.len	= mtd_erase_size;
 | |
| 		if (mtd_erase(mtd, &ei))
 | |
| 			goto re_write;
 | |
| 
 | |
| 		nand_info.page_offset = 0;
 | |
| 	}
 | |
| 
 | |
| 	err = mtd_write(mtd, nand_info.blk_offset + nand_info.page_offset,
 | |
| 			nand_info.ops_size, &bytes_write, (u8 *)g_vendor);
 | |
| 	nand_info.page_offset += nand_info.ops_size;
 | |
| 	if (err)
 | |
| 		goto re_write;
 | |
| 
 | |
| 	count++;
 | |
| 	/* write 2 copies for reliability */
 | |
| 	if (count < 2)
 | |
| 		goto re_write;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtd_vendor_storage_init(void)
 | |
| {
 | |
| 	int err, offset;
 | |
| 	size_t bytes_read;
 | |
| 	struct erase_info ei;
 | |
| 
 | |
| 	mtd = get_mtd_device_nm(vendor_mtd_name);
 | |
| 	if (IS_ERR(mtd))
 | |
| 		return -EIO;
 | |
| 
 | |
| 	nand_info.page_offset = 0;
 | |
| 	nand_info.blk_offset = 0;
 | |
| 	nand_info.version = 0;
 | |
| 	nand_info.ops_size = (sizeof(*g_vendor) + mtd->writesize - 1) / mtd->writesize;
 | |
| 	nand_info.ops_size *= mtd->writesize;
 | |
| 
 | |
| 	/*
 | |
| 	 * The NOR FLASH erase size maybe config as 4KB, need to re-define
 | |
| 	 * and maintain consistency with uboot.
 | |
| 	 */
 | |
| 	mtd_erase_size = mtd->erasesize;
 | |
| 	if (mtd_erase_size <= MTD_VENDOR_NOR_BLOCK_SIZE * 512)
 | |
| 		mtd_erase_size = MTD_VENDOR_NOR_BLOCK_SIZE * 512;
 | |
| 
 | |
| 	for (offset = 0; offset < mtd->size; offset += mtd_erase_size) {
 | |
| 		if (!mtd_block_isbad(mtd, offset)) {
 | |
| 			err = mtd_read(mtd, offset, sizeof(*g_vendor),
 | |
| 				       &bytes_read, (u8 *)g_vendor);
 | |
| 			if (err && err != -EUCLEAN)
 | |
| 				continue;
 | |
| 			if (bytes_read == sizeof(*g_vendor) &&
 | |
| 			    g_vendor->tag == MTD_VENDOR_TAG &&
 | |
| 			    g_vendor->version == g_vendor->version2) {
 | |
| 				if (g_vendor->version > nand_info.version) {
 | |
| 					nand_info.version = g_vendor->version;
 | |
| 					nand_info.blk_offset = offset;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (nand_info.blk_offset == offset)
 | |
| 			nand_info.blk_offset += mtd_erase_size;
 | |
| 	}
 | |
| 
 | |
| 	if (nand_info.version) {
 | |
| 		for (offset = mtd_erase_size - nand_info.ops_size;
 | |
| 		     offset >= 0;
 | |
| 		     offset -= nand_info.ops_size) {
 | |
| 			err = mtd_read(mtd, nand_info.blk_offset + offset,
 | |
| 				       sizeof(*g_vendor),
 | |
| 				       &bytes_read,
 | |
| 				       (u8 *)g_vendor);
 | |
| 
 | |
| 			/* the page is not programmed */
 | |
| 			if (!err && bytes_read == sizeof(*g_vendor) &&
 | |
| 			    g_vendor->tag == 0xFFFFFFFF &&
 | |
| 			    g_vendor->version == 0xFFFFFFFF &&
 | |
| 			    g_vendor->version2 == 0xFFFFFFFF)
 | |
| 				continue;
 | |
| 
 | |
| 			/* point to the next free page */
 | |
| 			if (nand_info.page_offset < offset)
 | |
| 				nand_info.page_offset = offset + nand_info.ops_size;
 | |
| 
 | |
| 			/* ecc error or io error */
 | |
| 			if (err && err != -EUCLEAN)
 | |
| 				continue;
 | |
| 
 | |
| 			if (bytes_read == sizeof(*g_vendor) &&
 | |
| 			    g_vendor->tag == MTD_VENDOR_TAG &&
 | |
| 			    g_vendor->version == g_vendor->version2) {
 | |
| 				if (nand_info.version > g_vendor->version)
 | |
| 					g_vendor->version = nand_info.version;
 | |
| 				else
 | |
| 					nand_info.version = g_vendor->version;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		memset((u8 *)g_vendor, 0, sizeof(*g_vendor));
 | |
| 		g_vendor->version = 1;
 | |
| 		g_vendor->tag = MTD_VENDOR_TAG;
 | |
| 		g_vendor->free_size = sizeof(g_vendor->data);
 | |
| 		g_vendor->version2 = g_vendor->version;
 | |
| 		for (offset = 0; offset < mtd->size; offset += mtd_erase_size) {
 | |
| 			if (!mtd_block_isbad(mtd, offset)) {
 | |
| 				memset(&ei, 0, sizeof(struct erase_info));
 | |
| 				ei.addr = nand_info.blk_offset + offset;
 | |
| 				ei.len  = mtd_erase_size;
 | |
| 				mtd_erase(mtd, &ei);
 | |
| 			}
 | |
| 		}
 | |
| 		mtd_vendor_nand_write();
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtd_vendor_read(u32 id, void *pbuf, u32 size)
 | |
| {
 | |
| 	u32 i;
 | |
| 
 | |
| 	if (!g_vendor)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (i = 0; i < g_vendor->item_num; i++) {
 | |
| 		if (g_vendor->item[i].id == id) {
 | |
| 			if (size > g_vendor->item[i].size)
 | |
| 				size = g_vendor->item[i].size;
 | |
| 			memcpy(pbuf,
 | |
| 			       &g_vendor->data[g_vendor->item[i].offset],
 | |
| 			       size);
 | |
| 			return size;
 | |
| 		}
 | |
| 	}
 | |
| 	return (-1);
 | |
| }
 | |
| 
 | |
| static int mtd_vendor_write(u32 id, void *pbuf, u32 size)
 | |
| {
 | |
| 	u32 i, j, align_size, alloc_size, item_num;
 | |
| 	u32 offset, next_size;
 | |
| 	u8 *p_data;
 | |
| 	struct vendor_item *item;
 | |
| 	struct vendor_item *next_item;
 | |
| 
 | |
| 	if (!g_vendor)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	p_data = g_vendor->data;
 | |
| 	item_num = g_vendor->item_num;
 | |
| 	align_size = ALIGN(size, 0x40); /* align to 64 bytes*/
 | |
| 	for (i = 0; i < item_num; i++) {
 | |
| 		item = &g_vendor->item[i];
 | |
| 		if (item->id == id) {
 | |
| 			alloc_size = ALIGN(item->size, 0x40);
 | |
| 			if (size > alloc_size) {
 | |
| 				if (g_vendor->free_size < align_size)
 | |
| 					return -1;
 | |
| 				offset = item->offset;
 | |
| 				for (j = i; j < item_num - 1; j++) {
 | |
| 					item = &g_vendor->item[j];
 | |
| 					next_item = &g_vendor->item[j + 1];
 | |
| 					item->id = next_item->id;
 | |
| 					item->size = next_item->size;
 | |
| 					item->offset = offset;
 | |
| 					next_size = ALIGN(next_item->size,
 | |
| 							  0x40);
 | |
| 					memcpy(&p_data[offset],
 | |
| 					       &p_data[next_item->offset],
 | |
| 					       next_size);
 | |
| 					offset += next_size;
 | |
| 				}
 | |
| 				item = &g_vendor->item[j];
 | |
| 				item->id = id;
 | |
| 				item->offset = offset;
 | |
| 				item->size = size;
 | |
| 				memcpy(&p_data[item->offset], pbuf, size);
 | |
| 				g_vendor->free_offset = offset + align_size;
 | |
| 				g_vendor->free_size = sizeof(g_vendor->data) - g_vendor->free_offset;
 | |
| 			} else {
 | |
| 				memcpy(&p_data[item->offset],
 | |
| 				       pbuf,
 | |
| 				       size);
 | |
| 				g_vendor->item[i].size = size;
 | |
| 			}
 | |
| 			g_vendor->version++;
 | |
| 			g_vendor->version2 = g_vendor->version;
 | |
| 			mtd_vendor_nand_write();
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (g_vendor->free_size >= align_size) {
 | |
| 		item = &g_vendor->item[g_vendor->item_num];
 | |
| 		item->id = id;
 | |
| 		item->offset = g_vendor->free_offset;
 | |
| 		item->size = size;
 | |
| 		g_vendor->free_offset += align_size;
 | |
| 		g_vendor->free_size -= align_size;
 | |
| 		memcpy(&g_vendor->data[item->offset], pbuf, size);
 | |
| 		g_vendor->item_num++;
 | |
| 		g_vendor->version++;
 | |
| 		g_vendor->version2 = g_vendor->version;
 | |
| 		mtd_vendor_nand_write();
 | |
| 		return 0;
 | |
| 	}
 | |
| 	return(-1);
 | |
| }
 | |
| 
 | |
| static int vendor_storage_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int vendor_storage_release(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static long vendor_storage_ioctl(struct file *file, unsigned int cmd,
 | |
| 				 unsigned long arg)
 | |
| {
 | |
| 	long ret = -1;
 | |
| 	int size;
 | |
| 	struct RK_VENDOR_REQ *v_req;
 | |
| 	u32 *page_buf;
 | |
| 
 | |
| 	page_buf = kmalloc(4096, GFP_KERNEL);
 | |
| 	if (!page_buf)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	mutex_lock(&vendor_ops_mutex);
 | |
| 
 | |
| 	v_req = (struct RK_VENDOR_REQ *)page_buf;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case VENDOR_READ_IO:
 | |
| 	{
 | |
| 		if (copy_from_user(page_buf, (void __user *)arg, 8)) {
 | |
| 			ret = -EFAULT;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (v_req->tag == VENDOR_REQ_TAG) {
 | |
| 			size = mtd_vendor_read(v_req->id, v_req->data,
 | |
| 						v_req->len);
 | |
| 			if (size != -1) {
 | |
| 				v_req->len = size;
 | |
| 				ret = 0;
 | |
| 				if (copy_to_user((void __user *)arg,
 | |
| 						 page_buf,
 | |
| 						 v_req->len + 8))
 | |
| 					ret = -EFAULT;
 | |
| 			}
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case VENDOR_WRITE_IO:
 | |
| 	{
 | |
| 		if (copy_from_user(page_buf, (void __user *)arg, 8)) {
 | |
| 			ret = -EFAULT;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (v_req->tag == VENDOR_REQ_TAG && (v_req->len < 4096 - 8)) {
 | |
| 			if (copy_from_user(page_buf, (void __user *)arg,
 | |
| 					   v_req->len + 8)) {
 | |
| 				ret = -EFAULT;
 | |
| 				break;
 | |
| 			}
 | |
| 			ret = mtd_vendor_write(v_req->id,
 | |
| 						v_req->data,
 | |
| 						v_req->len);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	default:
 | |
| 		ret = -EINVAL;
 | |
| 		goto exit;
 | |
| 	}
 | |
| exit:
 | |
| 	mutex_unlock(&vendor_ops_mutex);
 | |
| 	kfree(page_buf);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct file_operations vendor_storage_fops = {
 | |
| 	.open = vendor_storage_open,
 | |
| 	.compat_ioctl	= vendor_storage_ioctl,
 | |
| 	.unlocked_ioctl = vendor_storage_ioctl,
 | |
| 	.release = vendor_storage_release,
 | |
| };
 | |
| 
 | |
| static struct miscdevice vendor_storage_dev = {
 | |
| 	.minor = MISC_DYNAMIC_MINOR,
 | |
| 	.name  = "vendor_storage",
 | |
| 	.fops  = &vendor_storage_fops,
 | |
| };
 | |
| 
 | |
| static int vendor_storage_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	int ret;
 | |
| 
 | |
| 	mtd = get_mtd_device_nm(vendor_mtd_name);
 | |
| 	if (IS_ERR(mtd))
 | |
| 		return -EPROBE_DEFER;
 | |
| 
 | |
| 	g_vendor = devm_kmalloc(dev, sizeof(*g_vendor), GFP_KERNEL | GFP_DMA);
 | |
| 	if (!g_vendor)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	ret = mtd_vendor_storage_init();
 | |
| 	if (ret) {
 | |
| 		g_vendor = NULL;
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = misc_register(&vendor_storage_dev);
 | |
| 	rk_vendor_register(mtd_vendor_read, mtd_vendor_write);
 | |
| 
 | |
| 	pr_err("mtd vendor storage:20200313 ret = %d\n", ret);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int vendor_storage_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	if (g_vendor) {
 | |
| 		misc_deregister(&vendor_storage_dev);
 | |
| 		g_vendor = NULL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct platform_device_id vendor_storage_ids[] = {
 | |
| 	{ "mtd_vendor_storage", },
 | |
| 	{ }
 | |
| };
 | |
| 
 | |
| static struct platform_driver vendor_storage_driver = {
 | |
| 	.probe  = vendor_storage_probe,
 | |
| 	.remove = vendor_storage_remove,
 | |
| 	.driver = {
 | |
| 		.name	= "mtd_vendor_storage",
 | |
| 	},
 | |
| 	.id_table	= vendor_storage_ids,
 | |
| };
 | |
| 
 | |
| static int __init vendor_storage_init(void)
 | |
| {
 | |
| 	struct platform_device *pdev;
 | |
| 	int ret;
 | |
| 
 | |
| 	g_idb_buffer = NULL;
 | |
| 	ret = platform_driver_register(&vendor_storage_driver);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	pdev = platform_device_register_simple("mtd_vendor_storage",
 | |
| 					       -1, NULL, 0);
 | |
| 	if (IS_ERR(pdev)) {
 | |
| 		platform_driver_unregister(&vendor_storage_driver);
 | |
| 		return PTR_ERR(pdev);
 | |
| 	}
 | |
| 	g_pdev = pdev;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static __exit void vendor_storage_deinit(void)
 | |
| {
 | |
| 	platform_device_unregister(g_pdev);
 | |
| 	platform_driver_unregister(&vendor_storage_driver);
 | |
| }
 | |
| 
 | |
| device_initcall_sync(vendor_storage_init);
 | |
| module_exit(vendor_storage_deinit);
 | |
| MODULE_LICENSE("GPL");
 |