| /* |
| * Copyright (c) 2018 MediaTek Inc. |
| * |
| * 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 <err.h> |
| #include <errno.h> |
| #include <lib/bio.h> |
| #include <lib/nftl.h> |
| #include <lib/partition.h> |
| #include <malloc.h> |
| #include <pow2.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #define BBT_RESERVED_BLOCK 4 |
| #define SGPT_RESERVED_BLOCK 4 |
| |
| static int nftl_bio_block_mapping(struct bdev *dev, struct nftl_info *info, |
| bnum_t *page) |
| { |
| struct nftl_info *part; |
| u64 offset, start = 0; |
| u32 i, ppb = info->erase_size / info->write_size; |
| u32 block_count; |
| bool align; |
| bnum_t cur_page; |
| int ret = 0; |
| |
| #ifdef SUPPORT_GPT_FIXED_LBS |
| block_count = dev->block_count * (dev->block_size / info->write_size); |
| #else |
| block_count = dev->block_count; |
| #endif |
| offset = (u64)info->write_size * (*page); |
| part = nftl_search_by_address(offset, &start); |
| if (part == NULL) { |
| cur_page = *page; |
| ret = nftl_block_isbad(info, cur_page); |
| align = (block_count - 1 - cur_page) / ppb; |
| /* block not in partitions */ |
| if (align && ret) { |
| i = 1; |
| do { |
| cur_page += ppb; |
| ret = nftl_block_isbad(info, cur_page); |
| i++; |
| } while (ret && i < SGPT_RESERVED_BLOCK); |
| } |
| if (ret == 0) |
| *page = cur_page; |
| else |
| ret = ERR_NOT_FOUND; |
| } else { |
| offset -= start; |
| *page = offset / info->write_size; |
| ret = nftl_block_mapping(part, page); |
| *page += start / info->write_size; |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t nftl_bio_read_block(struct bdev *dev, void *buf, bnum_t block, |
| uint count) |
| { |
| struct nftl_info *info = nftl_open(dev->name); |
| uint32_t page_per_block = info->erase_size / info->write_size; |
| uint32_t read_count; |
| ssize_t err = 0, bytes_read = 0; |
| bnum_t phy_block; |
| |
| #ifdef SUPPORT_GPT_FIXED_LBS |
| count *= (dev->block_size / info->write_size); |
| block *= (dev->block_size / info->write_size); |
| #endif |
| while (count) { |
| read_count = page_per_block - (block % page_per_block); |
| read_count = MIN(read_count, count); |
| phy_block = block; |
| err = nftl_bio_block_mapping(dev, info, &phy_block); |
| if (err < 0) |
| break; |
| err = nftl_read(info, buf, phy_block * info->write_size, |
| read_count * info->write_size); |
| if (err < 0) |
| break; |
| bytes_read += read_count * info->write_size; |
| count -= read_count; |
| block += read_count; |
| buf += read_count * info->write_size; |
| } |
| |
| return (err < 0) ? err : bytes_read; |
| } |
| |
| static ssize_t nftl_bio_write_block(struct bdev *dev, const void *buf, |
| bnum_t block, uint count) |
| { |
| struct nftl_info *info = nftl_open(dev->name); |
| uint32_t page_per_block = info->erase_size / info->write_size; |
| uint32_t write_count; |
| ssize_t err = 0, bytes_write = 0; |
| bnum_t phy_block; |
| |
| #ifdef SUPPORT_GPT_FIXED_LBS |
| count *= (dev->block_size / info->write_size); |
| block *= (dev->block_size / info->write_size); |
| #endif |
| while (count) { |
| write_count = page_per_block - (block % page_per_block); |
| write_count = MIN(write_count, count); |
| phy_block = block; |
| err = nftl_bio_block_mapping(dev, info, &phy_block); |
| if (err < 0) |
| break; |
| err = nftl_write(info, buf, phy_block * info->write_size, |
| write_count * info->write_size); |
| if (err < 0) |
| break; |
| bytes_write += write_count * info->write_size; |
| count -= write_count; |
| block += write_count; |
| buf += write_count * info->write_size; |
| } |
| |
| return (err < 0) ? err : bytes_write; |
| } |
| |
| static ssize_t nftl_bio_erase(struct bdev *dev, off_t offset, size_t len) |
| { |
| u32 blocks; |
| ssize_t erase_len = 0, ret = 0; |
| struct nftl_info *info = nftl_open(dev->name); |
| bnum_t erase_page; |
| |
| if (offset % info->erase_size) |
| return ERR_INVALID_ARGS; |
| if (len % info->erase_size) |
| return ERR_INVALID_ARGS; |
| if (len == 0) |
| return 0; |
| |
| blocks = len / info->erase_size; |
| |
| while (blocks) { |
| erase_page = offset / info->write_size; |
| if (nftl_bio_block_mapping(dev, info, &erase_page) >= 0) { |
| ret = nftl_erase(info, erase_page * info->write_size, info->erase_size); |
| if (ret < 0) |
| break; |
| erase_len += info->erase_size; |
| } |
| offset += info->erase_size; |
| blocks--; |
| } |
| |
| return (ret < 0) ? ret : erase_len; |
| } |
| |
| static int nftl_bio_ioctl(struct bdev *dev, int request, void *argp) |
| { |
| struct nftl_info *info = nftl_open(dev->name); |
| |
| return info->ioctl(info, request, argp); |
| } |
| |
| int nftl_mount_bdev(struct nftl_info *info) |
| { |
| struct bdev *dev; |
| bio_erase_geometry_info_t *geometry; |
| u32 lba_count, lba_size; |
| |
| dev = (struct bdev *)malloc(sizeof(struct bdev)); |
| if (!dev) { |
| dprintf(CRITICAL, "%s: no enough memory\n", __func__); |
| return -ENOMEM; |
| } |
| |
| memset(dev, 0, sizeof(struct bdev)); |
| |
| geometry = malloc(sizeof(bio_erase_geometry_info_t)); |
| if (!geometry) { |
| dprintf(CRITICAL, "%s: no enough memory for geometry\n", __func__); |
| free(dev); |
| return -ENOMEM; |
| } |
| |
| #ifdef SUPPORT_GPT_FIXED_LBS |
| lba_size = 4096; |
| #else |
| lba_size = info->write_size; |
| #endif |
| lba_count = info->total_size / lba_size; |
| /* reserve 4blocks for bbt and 4blocks for SGPT */ |
| lba_count -= (BBT_RESERVED_BLOCK * info->erase_size / lba_size); |
| lba_count -= (SGPT_RESERVED_BLOCK * info->erase_size / lba_size); |
| |
| geometry->start = 0; |
| geometry->size = lba_count * lba_size; |
| geometry->erase_size = info->erase_size; |
| geometry->erase_shift = log2_uint(info->erase_size); |
| |
| bio_initialize_bdev(dev, info->name, lba_size, lba_count, |
| 1, geometry, BIO_FLAGS_NONE); |
| dev->read_block = nftl_bio_read_block; |
| dev->write_block = nftl_bio_write_block; |
| dev->erase = nftl_bio_erase; |
| dev->erase_byte = 0xff; |
| dev->ioctl = nftl_bio_ioctl; |
| bio_register_device(dev); |
| |
| partition_publish(info->name, 0); |
| |
| return 0; |
| } |