/*
 * Utility functions operating the devices. support only RAM disk device. added @lab4_1.
 */

#include "ramdev.h"
#include "vfs.h"
#include "pmm.h"
#include "riscv.h"
#include "util/types.h"
#include "util/string.h"
#include "spike_interface/spike_utils.h"
#include "rfs.h"

struct rfs_device *rfs_device_list[MAX_RAMDISK_COUNT];

//
// write the content stored in "buff" to the "blkno"^th block of disk.
//
int ramdisk_write(struct rfs_device *rfs_device, int blkno){
  if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT )
    panic("ramdisk_write: write block No %d out of range!\n", blkno);
  void * dst = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE);
  memcpy(dst, rfs_device->iobuffer, RAMDISK_BLOCK_SIZE);
  return 0;
}

//
// read the "blkno"^th block from the RAM disk and store its content into buffer.
//
int ramdisk_read(struct rfs_device *rfs_device, int blkno){
  if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT )
    panic("ramdisk_read: read block No out of range!\n");
  void * src = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE);
  memcpy(rfs_device->iobuffer, src, RAMDISK_BLOCK_SIZE);
  return 0;
}

//
// alloc RAMDISK_BLOCK_COUNT continuous pages (blocks) for the RAM Disk
// setup an vfs node, initialize RAM disk device, and attach the device with the vfs node.
//
struct device *init_rfs_device(const char *dev_name) {
  // find rfs in registered fs list
  struct file_system_type *fs_type = NULL;
  for (int i = 0; i < MAX_SUPPORTED_FS; i++) {
    if (fs_list[i] != NULL && fs_list[i]->type_num == RFS_TYPE) {
      fs_type = fs_list[i];
      break; 
    }
  }
  if (!fs_type) {
    panic("No RFS file system found!\n");
  }

  // alloc blocks for the RAM Disk
  void *curr_addr = NULL;
  void *last_addr = NULL;
  void *ramdisk_addr = NULL;
  for ( int i = 0; i < RAMDISK_BLOCK_COUNT; ++ i ){
    last_addr = curr_addr;
    curr_addr = alloc_page();
    if ( last_addr != NULL && last_addr - curr_addr != PGSIZE ){
      panic("RAM Disk0: address is discontinuous!\n");
    }
  }
  ramdisk_addr = curr_addr;

  // find a free rfs device
  struct rfs_device **rfs_device = NULL;
  int device_id = 0;
  for (int i = 0; i < MAX_RAMDISK_COUNT; i++) {
    if (rfs_device_list[i] == NULL) {
      rfs_device = &rfs_device_list[i];
      device_id = i;
      break;
    }
  }
  if (!rfs_device) {
    panic("RAM Disk0: no free device!\n");
  }
  
  *rfs_device = (struct rfs_device *)alloc_page();
  (*rfs_device)->d_blocks = RAMDISK_BLOCK_COUNT;
  (*rfs_device)->d_blocksize = RAMDISK_BLOCK_SIZE;
  (*rfs_device)->d_write = ramdisk_write;
  (*rfs_device)->d_read = ramdisk_read;
  (*rfs_device)->d_address = ramdisk_addr;
  (*rfs_device)->iobuffer = alloc_page();

  // allocate a vfs device
  struct device * device = (struct device *)alloc_page();
  // set the device name and index
  strcpy(device->dev_name, dev_name);
  device->dev_id = device_id;
  device->fs_type = fs_type;

  // add the device to the vfs device list
  for(int i = 0; i < MAX_VFS_DEV; i++) {
    if (vfs_dev_list[i] == NULL) {
      vfs_dev_list[i] = device;
      break;
    }
  }

  sprint("%s: base address of %s is: %p\n",dev_name, dev_name, ramdisk_addr);
  return device;
}