fs: ext4l: Add buffer_head I/O infrastructure
Add support.c with buffer_head I/O infrastructure for ext4l: - Buffer cache for caching buffer_heads across lookups - Buffer allocation/free functions - Block I/O functions (sb_getblk, sb_bread, brelse, submit_bh, bh_read) This keeps interface.c focused on the U-Boot filesystem layer interface. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# Makefile for the ext4l filesystem (Linux port)
|
||||
#
|
||||
|
||||
obj-y := interface.o stub.o
|
||||
obj-y := interface.o support.o stub.o
|
||||
|
||||
obj-y += balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
|
||||
extents_status.o file.o fsmap.o fsync.o hash.o ialloc.o \
|
||||
|
||||
@@ -313,9 +313,9 @@ extern struct user_namespace init_user_ns;
|
||||
#define __bforget(bh) do { } while (0)
|
||||
#define mark_buffer_dirty_inode(bh, i) do { } while (0)
|
||||
#define mark_buffer_dirty(bh) do { } while (0)
|
||||
#define lock_buffer(bh) do { } while (0)
|
||||
#define unlock_buffer(bh) do { } while (0)
|
||||
#define sb_getblk(sb, block) ((struct buffer_head *)NULL)
|
||||
#define lock_buffer(bh) set_buffer_locked(bh)
|
||||
#define unlock_buffer(bh) clear_buffer_locked(bh)
|
||||
struct buffer_head *sb_getblk(struct super_block *sb, sector_t block);
|
||||
#define test_clear_buffer_dirty(bh) ({ (void)(bh); 0; })
|
||||
#define wait_on_bit_io(addr, bit, mode) do { (void)(addr); (void)(bit); (void)(mode); } while (0)
|
||||
|
||||
@@ -1026,7 +1026,7 @@ static inline unsigned long memweight(const void *ptr, size_t bytes)
|
||||
#define rwsem_is_locked(sem) (1)
|
||||
|
||||
/* Buffer operations */
|
||||
#define sb_getblk_gfp(sb, blk, gfp) ((struct buffer_head *)NULL)
|
||||
#define sb_getblk_gfp(sb, blk, gfp) sb_getblk((sb), (blk))
|
||||
#define bh_uptodate_or_lock(bh) (1)
|
||||
/* ext4_read_bh is stubbed in interface.c */
|
||||
|
||||
@@ -1870,7 +1870,14 @@ struct file_system_type {
|
||||
#define FS_ALLOW_IDMAP 32
|
||||
|
||||
/* Buffer read sync */
|
||||
#define end_buffer_read_sync NULL
|
||||
static inline void end_buffer_read_sync(struct buffer_head *bh, int uptodate)
|
||||
{
|
||||
if (uptodate)
|
||||
set_buffer_uptodate(bh);
|
||||
else
|
||||
clear_buffer_uptodate(bh);
|
||||
unlock_buffer(bh);
|
||||
}
|
||||
#define REQ_OP_READ 0
|
||||
|
||||
/* Superblock flags */
|
||||
@@ -2377,7 +2384,7 @@ void dquot_free_block(struct inode *inode, loff_t nr);
|
||||
|
||||
/* Block device file operations - stubs */
|
||||
#define set_blocksize(f, size) ({ (void)(f); (void)(size); 0; })
|
||||
#define __bread(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; })
|
||||
struct buffer_head *__bread(struct block_device *bdev, sector_t block, unsigned size);
|
||||
|
||||
/* Trace stubs for super.c */
|
||||
#define trace_ext4_sync_fs(sb, wait) do { (void)(sb); (void)(wait); } while (0)
|
||||
@@ -2823,7 +2830,16 @@ struct wait_bit_entry {
|
||||
#define filemap_fdatawait_range_keep_errors(m, s, e) \
|
||||
({ (void)(m); (void)(s); (void)(e); 0; })
|
||||
#define crc32_be(crc, p, len) crc32(crc, p, len)
|
||||
#define free_buffer_head(bh) kfree(bh)
|
||||
void free_buffer_head(struct buffer_head *bh);
|
||||
|
||||
/* ext4l support functions (support.c) */
|
||||
void bh_cache_clear(void);
|
||||
int ext4l_read_block(sector_t block, size_t size, void *buffer);
|
||||
|
||||
/* ext4l interface functions (interface.c) */
|
||||
struct blk_desc *ext4l_get_blk_dev(void);
|
||||
struct disk_partition *ext4l_get_partition(void);
|
||||
|
||||
#define sb_is_blkdev_sb(sb) ({ (void)(sb); 0; })
|
||||
|
||||
/* DEFINE_WAIT stub - creates a wait queue entry */
|
||||
@@ -2850,7 +2866,7 @@ struct wait_bit_entry {
|
||||
#define trace_jbd2_lock_buffer_stall(...) do { } while (0)
|
||||
|
||||
/* JBD2 journal.c stubs */
|
||||
#define alloc_buffer_head(gfp) ((struct buffer_head *)kzalloc(sizeof(struct buffer_head), gfp))
|
||||
struct buffer_head *alloc_buffer_head(gfp_t gfp_mask);
|
||||
#define __getblk(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; })
|
||||
#define bmap(inode, block) ({ (void)(inode); (void)(block); 0; })
|
||||
#define trace_jbd2_update_log_tail(j, t, b, f) \
|
||||
@@ -2897,8 +2913,8 @@ loff_t seq_lseek(struct file *f, loff_t o, int w);
|
||||
do { (void)(j); (void)(f); } while (0)
|
||||
|
||||
/* Block device operations for journal.c */
|
||||
#define bh_read(bh, flags) ({ (void)(bh); (void)(flags); 0; })
|
||||
#define bh_read_nowait(bh, flags) do { (void)(bh); (void)(flags); } while (0)
|
||||
int bh_read(struct buffer_head *bh, int flags);
|
||||
#define bh_read_nowait(bh, flags) bh_read(bh, flags)
|
||||
#define bh_readahead_batch(n, bhs, f) do { (void)(n); (void)(bhs); (void)(f); } while (0)
|
||||
#define truncate_inode_pages_range(m, s, e) \
|
||||
do { (void)(m); (void)(s); (void)(e); } while (0)
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*
|
||||
* This provides the minimal interface between U-Boot and the ext4l driver.
|
||||
* This provides the interface between U-Boot's filesystem layer and
|
||||
* the ext4l driver.
|
||||
*/
|
||||
|
||||
#include <blk.h>
|
||||
#include <part.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/jbd2.h>
|
||||
#include <linux/types.h>
|
||||
@@ -23,6 +23,58 @@
|
||||
static struct blk_desc *ext4l_dev_desc;
|
||||
static struct disk_partition ext4l_part;
|
||||
|
||||
/* Global block device tracking for buffer I/O */
|
||||
static struct blk_desc *ext4l_blk_dev;
|
||||
static struct disk_partition ext4l_partition;
|
||||
static int ext4l_mounted;
|
||||
|
||||
/**
|
||||
* ext4l_get_blk_dev() - Get the current block device
|
||||
* Return: Block device descriptor or NULL if not mounted
|
||||
*/
|
||||
struct blk_desc *ext4l_get_blk_dev(void)
|
||||
{
|
||||
if (!ext4l_mounted)
|
||||
return NULL;
|
||||
return ext4l_blk_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* ext4l_get_partition() - Get the current partition info
|
||||
* Return: Partition info pointer
|
||||
*/
|
||||
struct disk_partition *ext4l_get_partition(void)
|
||||
{
|
||||
return &ext4l_partition;
|
||||
}
|
||||
|
||||
/**
|
||||
* ext4l_set_blk_dev() - Set the block device for ext4l operations
|
||||
* @blk_dev: Block device descriptor
|
||||
* @partition: Partition info (can be NULL for whole disk)
|
||||
*/
|
||||
void ext4l_set_blk_dev(struct blk_desc *blk_dev, struct disk_partition *partition)
|
||||
{
|
||||
ext4l_blk_dev = blk_dev;
|
||||
if (partition)
|
||||
memcpy(&ext4l_partition, partition, sizeof(struct disk_partition));
|
||||
else
|
||||
memset(&ext4l_partition, 0, sizeof(struct disk_partition));
|
||||
ext4l_mounted = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* ext4l_clear_blk_dev() - Clear block device (unmount)
|
||||
*/
|
||||
void ext4l_clear_blk_dev(void)
|
||||
{
|
||||
/* Clear buffer cache before unmounting */
|
||||
bh_cache_clear();
|
||||
|
||||
ext4l_blk_dev = NULL;
|
||||
ext4l_mounted = 0;
|
||||
}
|
||||
|
||||
int ext4l_probe(struct blk_desc *fs_dev_desc,
|
||||
struct disk_partition *fs_partition)
|
||||
{
|
||||
@@ -37,6 +89,9 @@ int ext4l_probe(struct blk_desc *fs_dev_desc,
|
||||
if (!fs_dev_desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set up block device for buffer I/O */
|
||||
ext4l_set_blk_dev(fs_dev_desc, fs_partition);
|
||||
|
||||
/* Initialise journal subsystem if enabled */
|
||||
if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) {
|
||||
ret = jbd2_journal_init_global();
|
||||
@@ -162,5 +217,6 @@ err_exit_es:
|
||||
|
||||
void ext4l_close(void)
|
||||
{
|
||||
ext4l_clear_blk_dev();
|
||||
ext4l_dev_desc = NULL;
|
||||
}
|
||||
|
||||
@@ -309,20 +309,14 @@ void *bdev_file_open_by_dev(dev_t dev, int flags, void *holder,
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block,
|
||||
unsigned int size, gfp_t gfp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/* bdev_getblk implemented in interface.c */
|
||||
|
||||
int trylock_buffer(struct buffer_head *bh)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void submit_bh(int op, struct buffer_head *bh)
|
||||
{
|
||||
}
|
||||
/* submit_bh implemented in interface.c */
|
||||
|
||||
/* NFS export stubs */
|
||||
struct dentry *generic_fh_to_parent(struct super_block *sb, struct fid *fid,
|
||||
@@ -519,6 +513,14 @@ void fs_put_dax(void *dax, void *holder)
|
||||
/* Block size */
|
||||
int sb_set_blocksize(struct super_block *sb, int size)
|
||||
{
|
||||
/* Validate block size */
|
||||
if (size != 1024 && size != 2048 && size != 4096)
|
||||
return 0;
|
||||
|
||||
/* Update superblock fields */
|
||||
sb->s_blocksize = size;
|
||||
sb->s_blocksize_bits = ffs(size) - 1;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
428
fs/ext4l/support.c
Normal file
428
fs/ext4l/support.c
Normal file
@@ -0,0 +1,428 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Internal support functions for ext4l filesystem
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*
|
||||
* This provides internal support functions for the ext4l driver,
|
||||
* including buffer_head I/O and buffer cache.
|
||||
*/
|
||||
|
||||
#include <blk.h>
|
||||
#include <part.h>
|
||||
#include <malloc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "ext4_uboot.h"
|
||||
#include "ext4.h"
|
||||
|
||||
/*
|
||||
* Buffer cache implementation
|
||||
*
|
||||
* Linux's sb_getblk() returns the same buffer_head for the same block number,
|
||||
* allowing flags like BH_Verified, BH_Uptodate, etc. to persist across calls.
|
||||
* This is critical for ext4's bitmap validation which sets buffer_verified()
|
||||
* and expects it to remain set on subsequent lookups.
|
||||
*/
|
||||
#define BH_CACHE_BITS 8
|
||||
#define BH_CACHE_SIZE (1 << BH_CACHE_BITS)
|
||||
#define BH_CACHE_MASK (BH_CACHE_SIZE - 1)
|
||||
|
||||
struct bh_cache_entry {
|
||||
struct buffer_head *bh;
|
||||
struct bh_cache_entry *next;
|
||||
};
|
||||
|
||||
static struct bh_cache_entry *bh_cache[BH_CACHE_SIZE];
|
||||
|
||||
static inline unsigned int bh_cache_hash(sector_t block)
|
||||
{
|
||||
return (unsigned int)(block & BH_CACHE_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* bh_cache_lookup() - Look up a buffer in the cache
|
||||
* @block: Block number to look up
|
||||
* @size: Expected block size
|
||||
* Return: Buffer head if found with matching size, NULL otherwise
|
||||
*/
|
||||
static struct buffer_head *bh_cache_lookup(sector_t block, size_t size)
|
||||
{
|
||||
unsigned int hash = bh_cache_hash(block);
|
||||
struct bh_cache_entry *entry;
|
||||
|
||||
for (entry = bh_cache[hash]; entry; entry = entry->next) {
|
||||
if (entry->bh && entry->bh->b_blocknr == block &&
|
||||
entry->bh->b_size == size) {
|
||||
atomic_inc(&entry->bh->b_count);
|
||||
return entry->bh;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* bh_cache_insert() - Insert a buffer into the cache
|
||||
* @bh: Buffer head to insert
|
||||
*/
|
||||
static void bh_cache_insert(struct buffer_head *bh)
|
||||
{
|
||||
unsigned int hash = bh_cache_hash(bh->b_blocknr);
|
||||
struct bh_cache_entry *entry;
|
||||
|
||||
/* Check if already in cache */
|
||||
for (entry = bh_cache[hash]; entry; entry = entry->next) {
|
||||
if (entry->bh && entry->bh->b_blocknr == bh->b_blocknr)
|
||||
return; /* Already cached */
|
||||
}
|
||||
|
||||
entry = malloc(sizeof(struct bh_cache_entry));
|
||||
if (!entry)
|
||||
return; /* Silently fail - cache is optional */
|
||||
|
||||
entry->bh = bh;
|
||||
entry->next = bh_cache[hash];
|
||||
bh_cache[hash] = entry;
|
||||
|
||||
/* Add a reference to keep the buffer alive in cache */
|
||||
atomic_inc(&bh->b_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* bh_cache_clear() - Clear the entire buffer cache
|
||||
*
|
||||
* Called on unmount to free all cached buffers.
|
||||
*/
|
||||
void bh_cache_clear(void)
|
||||
{
|
||||
int i;
|
||||
struct bh_cache_entry *entry, *next;
|
||||
|
||||
for (i = 0; i < BH_CACHE_SIZE; i++) {
|
||||
for (entry = bh_cache[i]; entry; entry = next) {
|
||||
next = entry->next;
|
||||
if (entry->bh) {
|
||||
/* Release the cache's reference */
|
||||
if (atomic_dec_and_test(&entry->bh->b_count))
|
||||
free_buffer_head(entry->bh);
|
||||
}
|
||||
free(entry);
|
||||
}
|
||||
bh_cache[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc_buffer_head() - Allocate a buffer_head structure
|
||||
* @gfp_mask: Allocation flags (ignored in U-Boot)
|
||||
* Return: Pointer to buffer_head or NULL on error
|
||||
*/
|
||||
struct buffer_head *alloc_buffer_head(gfp_t gfp_mask)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
|
||||
bh = malloc(sizeof(struct buffer_head));
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
memset(bh, 0, sizeof(struct buffer_head));
|
||||
|
||||
/* Note: b_data will be allocated when needed by read functions */
|
||||
atomic_set(&bh->b_count, 1);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc_buffer_head_with_data() - Allocate a buffer_head with data buffer
|
||||
* @size: Size of the data buffer to allocate
|
||||
* Return: Pointer to buffer_head or NULL on error
|
||||
*/
|
||||
static struct buffer_head *alloc_buffer_head_with_data(size_t size)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
|
||||
bh = malloc(sizeof(struct buffer_head));
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
memset(bh, 0, sizeof(struct buffer_head));
|
||||
|
||||
bh->b_data = malloc(size);
|
||||
if (!bh->b_data) {
|
||||
free(bh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bh->b_size = size;
|
||||
/* Allocate a folio for kmap_local_folio() to work */
|
||||
bh->b_folio = malloc(sizeof(struct folio));
|
||||
if (bh->b_folio) {
|
||||
memset(bh->b_folio, 0, sizeof(struct folio));
|
||||
bh->b_folio->data = bh->b_data;
|
||||
}
|
||||
atomic_set(&bh->b_count, 1);
|
||||
/* Mark that this buffer owns its b_data and should free it */
|
||||
set_bit(BH_OwnsData, &bh->b_state);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* free_buffer_head() - Free a buffer_head
|
||||
* @bh: Buffer head to free
|
||||
*
|
||||
* Only free b_data if BH_OwnsData is set. Shadow buffers created by
|
||||
* jbd2_journal_write_metadata_buffer() share b_data with the original
|
||||
* buffer and should not free it.
|
||||
*/
|
||||
void free_buffer_head(struct buffer_head *bh)
|
||||
{
|
||||
if (!bh)
|
||||
return;
|
||||
|
||||
/* Only free b_data if this buffer owns it */
|
||||
if (bh->b_data && test_bit(BH_OwnsData, &bh->b_state))
|
||||
free(bh->b_data);
|
||||
if (bh->b_folio)
|
||||
free(bh->b_folio);
|
||||
free(bh);
|
||||
}
|
||||
|
||||
/**
|
||||
* ext4l_read_block() - Read a block from the block device
|
||||
* @block: Block number (filesystem block, not sector)
|
||||
* @size: Block size in bytes
|
||||
* @buffer: Destination buffer
|
||||
* Return: 0 on success, negative on error
|
||||
*/
|
||||
int ext4l_read_block(sector_t block, size_t size, void *buffer)
|
||||
{
|
||||
struct blk_desc *blk_dev;
|
||||
struct disk_partition *part;
|
||||
lbaint_t sector;
|
||||
lbaint_t sector_count;
|
||||
unsigned long n;
|
||||
|
||||
blk_dev = ext4l_get_blk_dev();
|
||||
part = ext4l_get_partition();
|
||||
if (!blk_dev)
|
||||
return -EIO;
|
||||
|
||||
/* Convert block to sector */
|
||||
sector = (block * size) / blk_dev->blksz + part->start;
|
||||
sector_count = size / blk_dev->blksz;
|
||||
|
||||
if (sector_count == 0)
|
||||
sector_count = 1;
|
||||
|
||||
n = blk_dread(blk_dev, sector, sector_count, buffer);
|
||||
if (n != sector_count)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sb_getblk() - Get a buffer, using cache if available
|
||||
* @sb: Super block
|
||||
* @block: Block number
|
||||
* Return: Buffer head or NULL on error
|
||||
*/
|
||||
struct buffer_head *sb_getblk(struct super_block *sb, sector_t block)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
|
||||
if (!sb)
|
||||
return NULL;
|
||||
|
||||
/* Check cache first - must match block number AND size */
|
||||
bh = bh_cache_lookup(block, sb->s_blocksize);
|
||||
if (bh)
|
||||
return bh;
|
||||
|
||||
/* Allocate new buffer */
|
||||
bh = alloc_buffer_head_with_data(sb->s_blocksize);
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
bh->b_blocknr = block;
|
||||
bh->b_bdev = sb->s_bdev;
|
||||
bh->b_size = sb->s_blocksize;
|
||||
|
||||
/* Don't read - just allocate with zeroed data */
|
||||
memset(bh->b_data, '\0', bh->b_size);
|
||||
|
||||
/* Add to cache */
|
||||
bh_cache_insert(bh);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* sb_bread() - Read a block via super_block
|
||||
* @sb: Super block
|
||||
* @block: Block number to read
|
||||
* Return: Buffer head or NULL on error
|
||||
*/
|
||||
struct buffer_head *sb_bread(struct super_block *sb, sector_t block)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
int ret;
|
||||
|
||||
if (!sb)
|
||||
return NULL;
|
||||
|
||||
bh = sb_getblk(sb, block);
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
/* If buffer is already up-to-date, return it without re-reading */
|
||||
if (buffer_uptodate(bh))
|
||||
return bh;
|
||||
|
||||
bh->b_blocknr = block;
|
||||
bh->b_bdev = sb->s_bdev;
|
||||
bh->b_size = sb->s_blocksize;
|
||||
|
||||
ret = ext4l_read_block(block, sb->s_blocksize, bh->b_data);
|
||||
if (ret) {
|
||||
brelse(bh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark buffer as up-to-date */
|
||||
set_buffer_uptodate(bh);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* brelse() - Release a buffer_head
|
||||
* @bh: Buffer head to release
|
||||
*/
|
||||
void brelse(struct buffer_head *bh)
|
||||
{
|
||||
if (!bh)
|
||||
return;
|
||||
|
||||
if (atomic_dec_and_test(&bh->b_count))
|
||||
free_buffer_head(bh);
|
||||
}
|
||||
|
||||
/**
|
||||
* __brelse() - Release a buffer_head (alternate API)
|
||||
* @bh: Buffer head to release
|
||||
*/
|
||||
void __brelse(struct buffer_head *bh)
|
||||
{
|
||||
brelse(bh);
|
||||
}
|
||||
|
||||
/**
|
||||
* bdev_getblk() - Get buffer via block_device
|
||||
* @bdev: Block device
|
||||
* @block: Block number
|
||||
* @size: Block size
|
||||
* @gfp: Allocation flags
|
||||
* Return: Buffer head or NULL
|
||||
*/
|
||||
struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block,
|
||||
unsigned size, gfp_t gfp)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
|
||||
/* Check cache first - must match block number AND size */
|
||||
bh = bh_cache_lookup(block, size);
|
||||
if (bh)
|
||||
return bh;
|
||||
|
||||
bh = alloc_buffer_head_with_data(size);
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
bh->b_blocknr = block;
|
||||
bh->b_bdev = bdev;
|
||||
bh->b_size = size;
|
||||
|
||||
/* Don't read - just allocate with zeroed data */
|
||||
memset(bh->b_data, 0, bh->b_size);
|
||||
|
||||
/* Add to cache */
|
||||
bh_cache_insert(bh);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* __bread() - Read a block via block_device
|
||||
* @bdev: Block device
|
||||
* @block: Block number to read
|
||||
* @size: Block size
|
||||
* Return: Buffer head or NULL on error
|
||||
*/
|
||||
struct buffer_head *__bread(struct block_device *bdev, sector_t block,
|
||||
unsigned size)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
int ret;
|
||||
|
||||
bh = alloc_buffer_head_with_data(size);
|
||||
if (!bh)
|
||||
return NULL;
|
||||
|
||||
bh->b_blocknr = block;
|
||||
bh->b_bdev = bdev;
|
||||
bh->b_size = size;
|
||||
|
||||
ret = ext4l_read_block(block, size, bh->b_data);
|
||||
if (ret) {
|
||||
free_buffer_head(bh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark buffer as up-to-date */
|
||||
set_bit(BH_Uptodate, &bh->b_state);
|
||||
|
||||
return bh;
|
||||
}
|
||||
|
||||
/**
|
||||
* submit_bh() - Submit a buffer_head for I/O
|
||||
* @op: Operation (REQ_OP_READ, REQ_OP_WRITE, etc.)
|
||||
* @bh: Buffer head to submit
|
||||
*/
|
||||
void submit_bh(int op, struct buffer_head *bh)
|
||||
{
|
||||
int ret;
|
||||
int op_type = op & 0xff; /* Mask out flags, keep operation type */
|
||||
|
||||
if (op_type == REQ_OP_READ) {
|
||||
ret = ext4l_read_block(bh->b_blocknr, bh->b_size, bh->b_data);
|
||||
if (ret) {
|
||||
clear_buffer_uptodate(bh);
|
||||
return;
|
||||
}
|
||||
set_buffer_uptodate(bh);
|
||||
} else if (op_type == REQ_OP_WRITE) {
|
||||
/* Write support not implemented yet */
|
||||
clear_buffer_uptodate(bh);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* bh_read() - Read a buffer_head from disk
|
||||
* @bh: Buffer head to read
|
||||
* @flags: Read flags
|
||||
* Return: 0 on success, negative on error
|
||||
*/
|
||||
int bh_read(struct buffer_head *bh, int flags)
|
||||
{
|
||||
if (!bh || !bh->b_data)
|
||||
return -EINVAL;
|
||||
|
||||
submit_bh(REQ_OP_READ | flags, bh);
|
||||
return buffer_uptodate(bh) ? 0 : -EIO;
|
||||
}
|
||||
@@ -40,6 +40,8 @@ enum bh_state_bits {
|
||||
BH_PrivateStart,/* not a state bit, but the first bit available
|
||||
* for private allocation by other entities
|
||||
*/
|
||||
/* U-Boot specific: marks buffer owns b_data and should free it */
|
||||
BH_OwnsData = BH_PrivateStart,
|
||||
};
|
||||
|
||||
#define MAX_BUF_PER_PAGE (PAGE_SIZE / 512)
|
||||
@@ -176,8 +178,8 @@ static inline void put_bh(struct buffer_head *bh)
|
||||
atomic_dec(&bh->b_count);
|
||||
}
|
||||
|
||||
/* Stubs for U-Boot */
|
||||
#define brelse(bh) do { if (bh) put_bh(bh); } while (0)
|
||||
#define __brelse(bh) do { put_bh(bh); } while (0)
|
||||
/* Buffer release functions - implemented in ext4l/interface.c */
|
||||
void brelse(struct buffer_head *bh);
|
||||
void __brelse(struct buffer_head *bh);
|
||||
|
||||
#endif /* _LINUX_BUFFER_HEAD_H */
|
||||
|
||||
Reference in New Issue
Block a user