Files
kernel/sys/block/blk.c
T

277 lines
7.4 KiB
C

#include "sys/block/part_gpt.h"
#include "user/errno.h"
#include "sys/block/blk.h"
#include "fs/node.h"
#include "sys/assert.h"
#include "fs/vfs.h"
#include "sys/string.h"
#include "fs/fs.h"
#include "sys/debug.h"
#include "sys/heap.h"
#include "sys/dev.h"
#include "sys/mm.h"
static struct block_cache *g_cache_head = NULL, *g_cache_tail = NULL;
struct blk_part {
struct blkdev *device;
uint64_t lba_start;
uint64_t size;
};
void blk_set_cache(struct blkdev *blk, size_t page_capacity) {
_assert(blk);
_assert(!(blk->flags & BLK_CACHE));
block_cache_init(&blk->cache, blk, MM_PAGE_SIZE, page_capacity);
if (g_cache_tail) {
g_cache_tail->g_next = &blk->cache;
} else {
g_cache_head = &blk->cache;
}
blk->cache.g_prev = g_cache_tail;
blk->cache.g_next = NULL;
g_cache_tail = &blk->cache;
blk->flags |= BLK_CACHE;
}
void blk_cache_release(struct blkdev *blk) {
_assert(blk->flags & BLK_CACHE);
struct block_cache *cache = &blk->cache;
block_cache_flush(cache);
struct block_cache *prev = cache->g_prev;
struct block_cache *next = cache->g_next;
if (prev) {
prev->g_next = next;
} else {
g_cache_head = next;
}
if (next) {
next->g_prev = prev;
} else {
g_cache_tail = prev;
}
block_cache_release(cache);
blk->flags &= ~BLK_CACHE;
}
void blk_sync_all(void) {
kdebug("Global cache sync\n");
for (struct block_cache *cache = g_cache_head; cache; cache = cache->g_next) {
block_cache_flush(cache);
}
}
int blk_mmap(struct blkdev *blk, uintptr_t base, size_t page_count, int prot, int flags) {
_assert(blk);
if (blk->mmap) {
return blk->mmap(blk, base, page_count, prot, flags);
} else {
return -EINVAL;
}
}
static inline ssize_t blk_read_really(struct blkdev *blk, void *buf, size_t off, size_t lim) {
if (blk->read) {
return blk->read(blk, buf, off, lim);
} else {
return -EINVAL;
}
}
ssize_t blk_read(struct blkdev *blk, void *buf, size_t off, size_t lim) {
_assert(blk);
if (blk->flags & BLK_CACHE) {
// Use cache to fetch pages
uintptr_t page;
size_t page_size = blk->cache.page_size;
size_t rem = lim;
size_t index = off / page_size;
size_t bread = 0;
int err;
while (rem) {
size_t blk_off = off % page_size;
size_t can = MIN(page_size - blk_off, rem);
if (block_cache_get(&blk->cache, index * page_size, &page) != 0) {
// Fetch page from device
if ((err = blk_read_really(blk, (void *) MM_VIRTUALIZE(page), index * page_size, page_size)) < 0) {
kerror("Read failed: %s\n", kstrerror(err));
panic("TODO: deal with to-cache reads\n");
}
}
memcpy(buf, (void *) MM_VIRTUALIZE(page + blk_off), can);
rem -= can;
buf += can;
bread += can;
++index;
}
return bread;
} else {
// Bypass the cache
return blk_read_really(blk, buf, off, lim);
}
}
static inline ssize_t blk_write_really(struct blkdev *blk, const void *buf, size_t off, size_t lim) {
if (blk->write) {
return blk->write(blk, buf, off, lim);
} else {
return -EINVAL;
}
}
int blk_page_sync(struct blkdev *blk, uintptr_t block_address, uintptr_t page) {
ssize_t res = blk_write_really(blk, (void *) MM_VIRTUALIZE(page), block_address, blk->cache.page_size);
if (res < 0) {
return res;
} else {
return ((size_t) res == blk->cache.page_size) ? 0 : -EIO;
}
}
ssize_t blk_write(struct blkdev *blk, const void *buf, size_t off, size_t lim) {
_assert(blk);
if (blk->flags & BLK_CACHE) {
uintptr_t page;
size_t page_size = blk->cache.page_size;
size_t rem = lim;
size_t index = off / page_size;
size_t bwritten = 0;
int err;
while (rem) {
size_t blk_off = off % page_size;
size_t can = MIN(page_size - blk_off, rem);
if (block_cache_get(&blk->cache, index * page_size, &page) != 0) {
// Fetch page
if ((err = blk_read_really(blk, (void *) MM_VIRTUALIZE(page), index * page_size, page_size)) < 0) {
kerror("Read failed: %s\n", kstrerror(err));
panic("TODO: deal with to-cache reads\n");
}
}
memcpy((void *) MM_VIRTUALIZE(page + blk_off), buf, can);
block_cache_mark_dirty(&blk->cache, index * page_size);
rem -= can;
buf += can;
bwritten += can;
++index;
}
return bwritten;
} else {
return blk_write_really(blk, buf, off, lim);
}
}
int blk_ioctl(struct blkdev *blk, unsigned long req, void *arg) {
_assert(blk);
if (blk->ioctl) {
return blk->ioctl(blk, req, arg);
} else {
return -EINVAL;
}
}
int blk_mount_auto(struct vnode *at, struct blkdev *blk, uint32_t flags, const char *opt) {
struct fs_class *cls;
int res;
if ((cls = fs_class_by_name("ext2")) != NULL &&
(res = vfs_mount_internal(at, blk, cls, flags, opt)) == 0) {
return 0;
}
return -EINVAL;
}
static ssize_t blk_part_write(struct blkdev *dev, const void *buf, size_t off, size_t count) {
struct blk_part *part = (struct blk_part *) dev->dev_data;
size_t part_size_bytes = part->size * 512;
size_t part_off_bytes = part->lba_start * 512;
if (off >= part_size_bytes) {
return -1;
}
size_t l = MIN(part_size_bytes - off, count);
return blk_write(part->device, buf, off + part_off_bytes, l);
}
static ssize_t blk_part_read(struct blkdev *dev, void *buf, size_t off, size_t count) {
struct blk_part *part = (struct blk_part *) dev->dev_data;
size_t part_size_bytes = part->size * 512;
size_t part_off_bytes = part->lba_start * 512;
if (off >= part_size_bytes) {
return -1;
}
size_t l = MIN(part_size_bytes - off, count);
return blk_read(part->device, buf, off + part_off_bytes, l);
}
int blk_add_part(struct vnode *of, int n, uint64_t lba, uint64_t size) {
// Allocate both a device and partition details
char *buf = kmalloc(sizeof(struct blk_part) + sizeof(struct blkdev));
_assert(buf);
struct blk_part *part = (struct blk_part *) (buf + sizeof(struct blkdev));
struct blkdev *dev = (struct blkdev *) buf;
dev->dev_data = part;
dev->read = blk_part_read;
dev->write = blk_part_write;
dev->flags = 0;
part->device = of->dev;
part->lba_start = lba;
part->size = size;
char name[16];
size_t len = strlen(of->name);
_assert(len < sizeof(name) - 2);
strncpy(name, of->name, len);
name[len] = '1' + n;
name[len + 1] = 0;
dev_add(DEV_CLASS_BLOCK, DEV_BLOCK_PART, dev, name);
return 0;
}
int blk_enumerate_partitions(struct blkdev *blk, struct vnode *node) {
// TODO: better differentiation for CD/DVD-drives than just assuming every
// drive with block size of 2048 is one
ssize_t res;
uint8_t buf[1024];
if (blk->block_size == 512) {
if ((res = blk_read(blk, buf, 0, 1024)) < 0) {
return res;
}
if (!strncmp((const char *) (buf + 512), "EFI PART", 8)) {
return blk_enumerate_gpt(blk, node, buf);
}
}
return 0;
}