summaryrefslogtreecommitdiff
path: root/lume-legacy/src/drivers
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /lume-legacy/src/drivers
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'lume-legacy/src/drivers')
-rw-r--r--lume-legacy/src/drivers/plic.cc77
-rw-r--r--lume-legacy/src/drivers/uart.cc204
-rw-r--r--lume-legacy/src/drivers/virtio_dist.cc382
3 files changed, 663 insertions, 0 deletions
diff --git a/lume-legacy/src/drivers/plic.cc b/lume-legacy/src/drivers/plic.cc
new file mode 100644
index 0000000..a975c44
--- /dev/null
+++ b/lume-legacy/src/drivers/plic.cc
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard Qin
+ */
+#include "common/types.h"
+#include "kernel/riscv.h"
+#include "drivers/plic.h"
+#include "drivers/uart.h"
+#include "kernel/fdt.h"
+
+// Helper Macros — all relative to PLIC base from FDT
+#define PLIC_BASE (g_devices.plic.base_addr)
+#define PLIC_PRIORITY (PLIC_BASE + 0x0)
+#define PLIC_PENDING (PLIC_BASE + 0x1000)
+
+#define PLIC_SENABLE_ADDR(hart) (PLIC_BASE + 0x2080 + (hart) * 0x100)
+#define PLIC_STHRESHOLD_ADDR(hart) (PLIC_BASE + 0x201000 + (hart) * 0x2000)
+#define PLIC_SCLAIM_ADDR(hart) (PLIC_BASE + 0x201004 + (hart) * 0x2000)
+
+// Helper for Volatile Access
+static inline volatile uint32 *REG32(uint64 addr)
+{
+ return reinterpret_cast<volatile uint32 *>(addr);
+}
+
+namespace PLIC
+{
+ void init()
+ {
+ // Set priority for UART IRQ
+ if (g_devices.uart.irq > 0)
+ *REG32(PLIC_PRIORITY + g_devices.uart.irq * 4) = 1;
+
+ // Set priority for all VirtIO IRQs
+ for (int i = 0; i < g_devices.virtio_count; i++)
+ {
+ if (g_devices.virtio[i].irq > 0)
+ *REG32(PLIC_PRIORITY + g_devices.virtio[i].irq * 4) = 1;
+ }
+ }
+
+ void inithart()
+ {
+ int hart = static_cast<int>(r_tp());
+
+ // Build enable bitmask from discovered devices
+ uint32 enable = 0;
+
+ if (g_devices.uart.irq > 0 && g_devices.uart.irq < 32)
+ enable |= (1 << g_devices.uart.irq);
+
+ for (int i = 0; i < g_devices.virtio_count; i++)
+ {
+ if (g_devices.virtio[i].irq > 0 && g_devices.virtio[i].irq < 32)
+ enable |= (1 << g_devices.virtio[i].irq);
+ }
+
+ *REG32(PLIC_SENABLE_ADDR(hart)) = enable;
+
+ // Set Priority Threshold to 0 (Allow all)
+ *REG32(PLIC_STHRESHOLD_ADDR(hart)) = 0;
+
+ Drivers::uart_puts("[PLIC] Hart Init Done.\n");
+ }
+
+ int claim()
+ {
+ int hart = static_cast<int>(r_tp());
+ return *REG32(PLIC_SCLAIM_ADDR(hart));
+ }
+
+ void complete(int irq)
+ {
+ int hart = static_cast<int>(r_tp());
+ *REG32(PLIC_SCLAIM_ADDR(hart)) = static_cast<uint32>(irq);
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/drivers/uart.cc b/lume-legacy/src/drivers/uart.cc
new file mode 100644
index 0000000..397a402
--- /dev/null
+++ b/lume-legacy/src/drivers/uart.cc
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "drivers/uart.h"
+#include "kernel/riscv.h"
+#include "kernel/spinlock.h"
+#include "kernel/proc.h"
+#include "kernel/mm.h"
+#include "lib/string.h"
+
+namespace Drivers
+{
+ struct
+ {
+ Spinlock lock;
+ char buf[128]; // Line buffer
+ uint32 r; // Read pointer (position taken by the user)
+ uint32 w; // Write pointer (position confirmed by Enter)
+ uint32 e; // Editing cursor (current typing position)
+ } cons;
+
+ // Circular buffer structure
+ struct
+ {
+ Spinlock lock;
+ char buf[UART_RX_BUF_SIZE];
+ uint32 r; // read index
+ uint32 w; // write index
+ } uart_rx;
+
+ Spinlock uart_tx_lock;
+
+ void uart_init()
+ {
+ uart_rx.lock.init("uart_rx");
+ cons.lock.init("console");
+ uart_tx_lock.init("uart_tx");
+ uart_rx.r = 0;
+ uart_rx.w = 0;
+
+ // Disable interrupts and configure
+ *(volatile uint8 *)(uart_base() + IER) = 0x00;
+
+ // Enter baud rate setting mode (LCR_DLAB) = 0x80;
+ *(volatile uint8 *)(uart_base() + LCR) = 0x80;
+ *(volatile uint8 *)(uart_base() + 0) = 0x03; // DLL: 38.4K baud
+ *(volatile uint8 *)(uart_base() + 1) = 0x00; // DLM
+
+ // Exit baud rate setting, set 8-bit word length, no parity
+ *(volatile uint8 *)(uart_base() + LCR) = 0x03;
+
+ // Reset and enable FIFO
+ *(volatile uint8 *)(uart_base() + FCR) = 0x07;
+
+ // Enable Receive Data Available Interrupt
+ *(volatile uint8 *)(uart_base() + IER) = 0x01;
+ }
+
+ void uart_putc(char c)
+ {
+ uart_tx_lock.acquire();
+ while ((*(volatile uint8 *)(uart_base() + LSR) & LSR_TX_IDLE) == 0)
+ ;
+ *(volatile uint8 *)(uart_base() + THR) = c;
+ uart_tx_lock.release();
+ }
+
+ void uart_puts(const char *s)
+ {
+ while (*s)
+ uart_putc(*s++);
+ }
+
+ void uart_put_int(int val)
+ {
+ char buf[16];
+ int i = 0;
+ if (val == 0)
+ {
+ uart_puts("0");
+ return;
+ }
+ if (val < 0)
+ {
+ uart_puts("-");
+ val = -val;
+ }
+ while (val > 0)
+ {
+ buf[i++] = (val % 10) + '0';
+ val /= 10;
+ }
+ while (i > 0)
+ {
+ uart_putc(buf[--i]);
+ }
+ }
+
+ void print_hex(uint64 val)
+ {
+ char hex[] = "0123456789ABCDEF";
+ uart_puts("0x");
+ for (int i = 60; i >= 0; i -= 4)
+ {
+ uart_putc(hex[(val >> i) & 0xF]);
+ }
+ }
+
+ void uart_intr()
+ {
+ uart_rx.lock.acquire();
+
+ while (1)
+ {
+ if ((*(volatile uint8 *)(uart_base() + LSR) & LSR_RX_READY) == 0)
+ break;
+
+ char c = *(volatile uint8 *)(uart_base() + RHR);
+
+ uart_rx.buf[uart_rx.w % UART_RX_BUF_SIZE] = c;
+ uart_rx.w++;
+
+ ProcManager::wakeup(&uart_rx);
+ }
+
+ uart_rx.lock.release();
+ }
+
+ // Modern console read: Line buffering + Editing Keys + Echo
+ int console_read(uint64 target, int len)
+ {
+ struct Proc *p = myproc();
+ int target_idx = 0;
+
+ cons.lock.acquire();
+
+ while (target_idx < len)
+ {
+ while (cons.r < cons.w)
+ {
+ char c = cons.buf[cons.r++ % sizeof(cons.buf)];
+ cons.lock.release();
+
+ if (VM::copyout(p->pagetable, target + target_idx, &c, 1) < 0)
+ return -1;
+
+ target_idx++;
+ if (c == '\n')
+ return target_idx;
+ if (target_idx == len)
+ return target_idx;
+
+ cons.lock.acquire();
+ }
+
+ uart_rx.lock.acquire();
+ while (uart_rx.r == uart_rx.w)
+ {
+ if (p->xstate)
+ {
+ uart_rx.lock.release();
+ cons.lock.release();
+ return -1;
+ }
+ cons.lock.release();
+ ProcManager::sleep(&uart_rx, &uart_rx.lock);
+ cons.lock.acquire();
+ }
+
+ char c = uart_rx.buf[uart_rx.r++ % UART_RX_BUF_SIZE];
+ uart_rx.lock.release();
+
+ if (c == 127 || c == '\b')
+ {
+ if (cons.e != cons.w)
+ {
+ cons.e--;
+ uart_putc('\b');
+ uart_putc(' ');
+ uart_putc('\b');
+ }
+ }
+ else
+ {
+ if (c == '\r')
+ c = '\n';
+
+ uart_putc(c);
+
+ cons.buf[cons.e++ % sizeof(cons.buf)] = c;
+
+ if (c == '\n' || cons.e == cons.w + sizeof(cons.buf))
+ {
+ cons.w = cons.e;
+ ProcManager::wakeup(&cons);
+ }
+ }
+ }
+
+ cons.lock.release();
+ return target_idx;
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/drivers/virtio_dist.cc b/lume-legacy/src/drivers/virtio_dist.cc
new file mode 100644
index 0000000..8292874
--- /dev/null
+++ b/lume-legacy/src/drivers/virtio_dist.cc
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "drivers/virtio.h"
+#include "kernel/fdt.h"
+#include "kernel/riscv.h"
+#include "kernel/pmm.h"
+#include "kernel/spinlock.h"
+#include "lib/string.h"
+#include "drivers/uart.h"
+#include "kernel/proc.h"
+#include "kernel/buf.h"
+
+static uint64 virtio_base = 0x10001000;
+
+// VirtIO Block Device Config Structure
+struct virtio_blk_config
+{
+ uint64 capacity;
+ uint32 size_max;
+ uint32 seg_max;
+ struct virtio_blk_geometry
+ {
+ uint16 cylinders;
+ uint8 heads;
+ uint8 sectors;
+ } geometry;
+ uint32 blk_size;
+ uint8 physical_block_exp;
+ uint8 alignment_offset;
+ uint16 min_io_size;
+ uint32 opt_io_size;
+} __attribute__((packed));
+
+static inline volatile uint32 *REG(uint32 offset)
+{
+ return reinterpret_cast<volatile uint32 *>(virtio_base + offset);
+}
+
+namespace VirtIO
+{
+ struct Disk
+ {
+ struct VRingDesc *desc;
+ struct VRingAvail *avail;
+ struct VRingUsed *used;
+
+ int free_head;
+ int num_free;
+ int free_chain[NUM_DESCS];
+
+ uint16 used_idx;
+
+ struct TraceInfo
+ {
+ int busy;
+ int status;
+ } info[NUM_DESCS];
+
+ struct BlockRequest reqs[NUM_DESCS];
+ uint8 req_status[NUM_DESCS];
+
+ Spinlock lock;
+ } disk;
+
+ void init()
+ {
+ disk.lock.init("virtio_disk");
+
+ // 1. Probe VirtIO devices discovered by FDT
+ bool found = false;
+ for (int i = 0; i < g_devices.virtio_count; i++)
+ {
+ uint64 addr = g_devices.virtio[i].base_addr;
+ if (addr == 0)
+ continue;
+
+ volatile uint32 *magic = (volatile uint32 *)(addr + VIRTIO_MMIO_MAGIC_VALUE);
+ volatile uint32 *dev_id = (volatile uint32 *)(addr + VIRTIO_MMIO_DEVICE_ID);
+
+ if (*magic == 0x74726976 && *dev_id == 2) // ID 2 = Block Device
+ {
+ virtio_base = addr;
+ found = true;
+ Drivers::uart_puts(ANSI_GREEN "[VirtIO] Found Block Device @ ");
+ Drivers::print_hex(addr);
+ Drivers::uart_puts(ANSI_RESET "\n");
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ Drivers::uart_puts(ANSI_RED "[VirtIO] Fatal - No Block Device found.\n" ANSI_RESET);
+ return;
+ }
+
+ // 2. Check Device Version (We want Modern v2)
+ if (*REG(VIRTIO_MMIO_VERSION) != 2)
+ {
+ Drivers::uart_puts(ANSI_YELLOW "[VirtIO] Warning: Legacy device detected (expected v2).\n" ANSI_RESET);
+ }
+
+ // 3. Reset Device
+ *REG(VIRTIO_MMIO_STATUS) = 0;
+
+ // 4. Set ACKNOWLEDGE & DRIVER status
+ uint32 status = *REG(VIRTIO_MMIO_STATUS);
+ status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER;
+ *REG(VIRTIO_MMIO_STATUS) = status;
+
+ // 5. Feature Negotiation
+ *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 0;
+ uint32 device_features_lo = *REG(VIRTIO_MMIO_DEVICE_FEATURES);
+ (void)device_features_lo;
+
+ *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 1;
+ uint32 device_features_hi = *REG(VIRTIO_MMIO_DEVICE_FEATURES);
+ (void)device_features_hi;
+
+ *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 0;
+ *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 0;
+
+ *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 1;
+ // VIRTIO_F_VERSION_1 is usually bit 32.
+ // If not defined in header, we hardcode logic here: (1ULL << 0) for high bits
+ *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 1;
+
+ status |= VIRTIO_CONFIG_S_FEATURES_OK;
+ *REG(VIRTIO_MMIO_STATUS) = status;
+
+ if (!(*REG(VIRTIO_MMIO_STATUS) & VIRTIO_CONFIG_S_FEATURES_OK))
+ {
+ Drivers::uart_puts("VirtIO: Feature negotiation failed.\n");
+ return;
+ }
+
+ // 6. Queue Configuration
+ *REG(VIRTIO_MMIO_QUEUE_SEL) = 0;
+ if (*REG(VIRTIO_MMIO_QUEUE_READY))
+ {
+ Drivers::uart_puts("VirtIO: Queue 0 not ready.\n");
+ }
+
+ uint32 max_queue = *REG(VIRTIO_MMIO_QUEUE_NUM_MAX);
+ if (max_queue < NUM_DESCS)
+ {
+ Drivers::uart_puts("VirtIO: Queue size too small.\n");
+ return;
+ }
+
+ *REG(VIRTIO_MMIO_QUEUE_NUM) = NUM_DESCS;
+
+ // Allocate Memory for VirtQueues
+ void *ptr = PMM::alloc_pages(1); // 4KB is enough for small rings
+ memset(ptr, 0, PGSIZE);
+
+ uint64 base_addr = (uint64)ptr;
+ disk.desc = reinterpret_cast<struct VRingDesc *>(base_addr);
+ disk.avail = reinterpret_cast<struct VRingAvail *>(base_addr + NUM_DESCS * 16);
+ disk.used = reinterpret_cast<struct VRingUsed *>(base_addr + PGSIZE / 2);
+
+ *REG(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint32)base_addr;
+ *REG(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint32)(base_addr >> 32);
+
+ uint64 avail_addr = (uint64)disk.avail;
+ *REG(VIRTIO_MMIO_QUEUE_DRIVER_LOW) = (uint32)avail_addr;
+ *REG(VIRTIO_MMIO_QUEUE_DRIVER_HIGH) = (uint32)(avail_addr >> 32);
+
+ uint64 used_addr = (uint64)disk.used;
+ *REG(VIRTIO_MMIO_QUEUE_DEVICE_LOW) = (uint32)used_addr;
+ *REG(VIRTIO_MMIO_QUEUE_DEVICE_HIGH) = (uint32)(used_addr >> 32);
+
+ *REG(VIRTIO_MMIO_QUEUE_READY) = 1;
+
+ // 7. Initialize Internal Structures
+ disk.free_head = 0;
+ disk.num_free = NUM_DESCS;
+ disk.used_idx = 0;
+ for (int i = 0; i < NUM_DESCS; i++)
+ {
+ disk.free_chain[i] = i + 1;
+ disk.info[i].busy = 0;
+ }
+
+ // 8. Finalize Setup
+ status |= VIRTIO_CONFIG_S_DRIVER_OK;
+ *REG(VIRTIO_MMIO_STATUS) = status;
+
+ // === [Feature] Read Capacity ===
+ volatile struct virtio_blk_config *config = (volatile struct virtio_blk_config *)(virtio_base + VIRTIO_MMIO_CONFIG);
+ uint64 capacity_sectors = config->capacity;
+ uint64 size_mb = (capacity_sectors * 512) / (1024 * 1024);
+
+ Drivers::uart_puts(ANSI_GREEN "[VirtIO] Initialized. Capacity: ");
+ Drivers::uart_put_int(size_mb);
+ Drivers::uart_puts(" MB\n" ANSI_RESET);
+ }
+
+ static int alloc_desc()
+ {
+ if (disk.num_free == 0)
+ return -1;
+ int id = disk.free_head;
+ disk.free_head = disk.free_chain[id];
+ disk.num_free--;
+ return id;
+ }
+
+ static void free_desc(int id)
+ {
+ disk.free_chain[id] = disk.free_head;
+ disk.free_head = id;
+ disk.num_free++;
+ ProcManager::wakeup(&disk.free_head);
+ }
+
+ static void free_chain_soft(int head_idx)
+ {
+ int curr = head_idx;
+ while (1)
+ {
+ int flag = disk.desc[curr].flags;
+ int next = disk.desc[curr].next;
+ free_desc(curr);
+ if (flag & VRING_DESC_F_NEXT)
+ curr = next;
+ else
+ break;
+ }
+ }
+
+ void rw(int write, uint64 blockno, char *buf)
+ {
+ uint64 sector = blockno * (1024 / 512); // Assume 1KB blocks
+
+ disk.lock.acquire();
+
+ // Wait for enough descriptors
+ while (disk.num_free < 3)
+ {
+ ProcManager::sleep(&disk.free_head, &disk.lock);
+ }
+
+ int idx[3];
+ idx[0] = alloc_desc();
+ idx[1] = alloc_desc();
+ idx[2] = alloc_desc();
+
+ // 1. Request Header
+ struct BlockRequest *req = &disk.reqs[idx[0]];
+ req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
+ req->reserved = 0;
+ req->sector = sector;
+
+ disk.desc[idx[0]].addr = (uint64)req;
+ disk.desc[idx[0]].len = sizeof(struct BlockRequest);
+ disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
+ disk.desc[idx[0]].next = idx[1];
+
+ // 2. Data Buffer
+ disk.desc[idx[1]].addr = (uint64)buf;
+ disk.desc[idx[1]].len = 1024; // Fixed 1KB for this test
+ disk.desc[idx[1]].flags = VRING_DESC_F_NEXT;
+ if (!write)
+ disk.desc[idx[1]].flags |= VRING_DESC_F_WRITE;
+ disk.desc[idx[1]].next = idx[2];
+
+ // 3. Status Byte
+ disk.desc[idx[2]].addr = (uint64)&disk.req_status[idx[0]];
+ disk.desc[idx[2]].len = 1;
+ disk.desc[idx[2]].flags = VRING_DESC_F_WRITE;
+ disk.desc[idx[2]].next = 0;
+
+ disk.info[idx[0]].busy = 1;
+
+ // Submit to Avail Ring
+ disk.avail->ring[disk.avail->idx % NUM_DESCS] = idx[0];
+ __sync_synchronize();
+ disk.avail->idx++;
+ __sync_synchronize();
+
+ // Notify Device
+ *REG(VIRTIO_MMIO_QUEUE_NOTIFY) = 0;
+
+ // Wait for Completion
+ while (disk.info[idx[0]].busy)
+ {
+ ProcManager::sleep(&disk.info[idx[0]], &disk.lock);
+ }
+
+ free_chain_soft(idx[0]);
+ disk.lock.release();
+ }
+
+ void intr()
+ {
+ disk.lock.acquire();
+
+ uint32 status = *REG(VIRTIO_MMIO_INTERRUPT_STATUS);
+ *REG(VIRTIO_MMIO_INTERRUPT_ACK) = status & 0x3;
+
+ volatile struct VRingUsed *used_ring = (volatile struct VRingUsed *)disk.used;
+
+ while (disk.used_idx != used_ring->idx)
+ {
+ int id = used_ring->ring[disk.used_idx % NUM_DESCS].id;
+
+ if (disk.info[id].busy)
+ {
+ disk.info[id].busy = 0;
+ ProcManager::wakeup(&disk.info[id]);
+ }
+ disk.used_idx++;
+ }
+
+ disk.lock.release();
+ }
+
+ void test_rw()
+ {
+ Drivers::uart_puts(ANSI_BLUE "[VirtIO] Disk Test Start...\n" ANSI_RESET);
+
+ char *buf = (char *)PMM::alloc_page();
+ if (!buf)
+ return;
+
+ const char *msg = "Lume OS Disk Driver Test Pattern";
+ strcpy(buf, msg);
+
+ rw(1, 0, buf); // Write
+ memset(buf, 0, PGSIZE);
+ rw(0, 0, buf); // Read
+
+ if (strcmp(buf, msg) == 0)
+ {
+ Drivers::uart_puts(ANSI_GREEN "[VirtIO] Test PASSED (R/W Verified).\n" ANSI_RESET);
+ }
+ else
+ {
+ Drivers::uart_puts(ANSI_RED "[VirtIO] Test FAILED.\n" ANSI_RESET);
+ }
+
+ PMM::free_page(buf);
+ }
+
+ void test_bio()
+ {
+ Drivers::uart_puts(ANSI_BLUE "[Bio] Starting Buffer Cache Test...\n" ANSI_RESET);
+
+ struct Buf *b = BufferCache::bread(0, 100);
+
+ Drivers::uart_puts("[Bio] Bread block 100 (1st time).\n");
+
+ b->data[0] = 'L';
+ b->data[1] = 'u';
+ b->data[2] = 'm';
+ b->data[3] = 'e';
+ b->data[4] = '\0';
+
+ BufferCache::bwrite(b);
+ Drivers::uart_puts("[Bio] Bwrite block 100.\n");
+
+ BufferCache::brelse(b);
+ Drivers::uart_puts("[Bio] Brelease block 100.\n");
+
+ struct Buf *b2 = BufferCache::bread(0, 100);
+ Drivers::uart_puts("[Bio] Bread block 100 (2nd time).\n");
+
+ if (b2->data[0] == 'L' && b2->data[1] == 'u' && b2->data[3] == 'e')
+ {
+ Drivers::uart_puts(ANSI_GREEN "[Bio] Data verification PASSED.\n" ANSI_RESET);
+ }
+ else
+ {
+ Drivers::uart_puts(ANSI_RED "[Bio] Data verification FAILED.\n" ANSI_RESET);
+ }
+
+ BufferCache::brelse(b2);
+ }
+} \ No newline at end of file