User space device driver implementation
// Includes #include <malloc.h> #include <string.h> // Project Includes #include <hdd_file_io.h> #include <hdd_driver.h> #include <cmpsc311_log.h> #include <cmpsc311_util.h> // Defines (you can ignore these) #define MAX_HDD_FILEDESCR 1024 #define HIO_UNIT_TEST_MAX_WRITE_SIZE 1024 #define HDD_IO_UNIT_TEST_ITERATIONS 10240 // Student Defines #define NUM_ENTRIES 5 //Let max number of open files be 5 for now #define ISOLATE_RESULT 2147483648 //This represents 1 followed by 32 0's. Used to isolate R // Type for UNIT test interface (you can ignore this) typedef enum { HIO_UNIT_TEST_READ = 0, HIO_UNIT_TEST_WRITE = 1, HIO_UNIT_TEST_APPEND = 2, HIO_UNIT_TEST_SEEK = 3, } HDD_UNIT_TEST_TYPE; // Struct to store file metadata struct myFile { int16_t handle; char *path; int32_t seekPos; int32_t blockID; }; // For this first assignment, we will assume no more then 5 files will be open at a time struct myFile openFiles[NUM_ENTRIES]; //This array will store 0 for an available entry, 1 for filled, //allowing us to keep track of what handles are available int filledLocation[NUM_ENTRIES] = {0,0,0,0,0}; //Set to 1 when program starts, in order to initialize device only once int programStarted = 0; // // Implementation //////////////////////////////////////////////////////////////////////////////// // // Function : generateHDDCommand // Description : Given parameters for hdd_data_lane, prepares the command to be sent // // Inputs : Opcode, Block Size, Block ID // Outputs : HddBitCmd // int64_t generateHDDCommand(int opCode,int blockSize, int blockID) { HddBitCmd sendCommand = 0; //Using bitwise operators, piece together the command into the //format described in hdd_driver.h sendCommand = sendCommand + opCode; sendCommand = sendCommand << 26; sendCommand = sendCommand + blockSize; sendCommand = sendCommand << 4; sendCommand = sendCommand << 32; sendCommand = sendCommand + blockID; return sendCommand; } //////////////////////////////////////////////////////////////////////////////// // // Function : hdd_open // Description : Opens a file, assigns a file handle for use in later functions // // Inputs : Pointer for the file path // Outputs : A file handle, or -1 if file already opened // int16_t hdd_open(char *path) { //If HDD has not been initialized, attempt to initialize it now if (programStarted == 0) { if (hdd_initialize() == -1) { //Can't initialize! return -1; } else { //Initialization successful. Set the boolean to true so it does not try again programStarted = 1; } } //First check if file is open by checking matches with currently open files for (int i = 0; i < NUM_ENTRIES; ++i) { if (filledLocation[i] == 1) { if (strcmp((openFiles[i].path),path) == 0) { //File already opened! return -1; } } else { //File will be opened. handle will be array index //Block ID of -1 will indicate that the file has not been opened filledLocation[i] = 1; openFiles[i].handle = i; (openFiles[i].path) = path; openFiles[i].seekPos = 0; openFiles[i].blockID = -1; //Return handle (array index) return i; } } //If all file spots full, return -1. Should not happen in this program. return -1; } //////////////////////////////////////////////////////////////////////////////// // // Function : hdd_close // Description : Closes the file associated with the file handle, deletes all related block contents // // Inputs : File handle integer // Outputs : 0 if successful, -1 if unsuccessful // int16_t hdd_close(int16_t fh) { //Make sure file is open if (filledLocation[fh] == 0) { //File not open! return -1; } //Delete the block associated with the file handle if (hdd_delete_block(openFiles[fh].blockID) == -1) { return -1; } //Allow that location to be rewritten filledLocation[fh] = 0; openFiles[fh].blockID = 0; openFiles[fh].handle = 0; openFiles[fh].path = ""; openFiles[fh].seekPos = 0; return 0; } //////////////////////////////////////////////////////////////////////////////// // // Function : hdd_read // Description : Reads a count number of bytes from the current seek position // for a file handle fh into the buffer data // // Inputs : File handle, data buffer, count number of bytes to read // Outputs : Number of bytes read, or -1 if unsuccessful // int32_t hdd_read(int16_t fh, void * data, int32_t count) { // Make sure file is open if (filledLocation[fh] == 0) { //File not open! return -1; } //Create variable for HDD return command HddBitResp returnCommand = 0; int64_t result = 0; int32_t blockSize = hdd_read_block_size(openFiles[fh].blockID); int32_t blockSeek = openFiles[fh].seekPos; //Create new character array to use as temporary buffer char *blockData = malloc(blockSize); //Using helper function, call data lane using our read parameters returnCommand = hdd_data_lane(generateHDDCommand(HDD_BLOCK_READ,blockSize,openFiles[fh].blockID),blockData); result = returnCommand & ISOLATE_RESULT; result = result >> 32; if (result == 0) { if (count > blockSize - blockSeek) { //Attempt to read past the end! Read only what is available memcpy(data,&blockData[blockSeek],blockSize-blockSeek); //Update seek, free memory, and return bytes read openFiles[fh].seekPos = blockSize; free(blockData); return (blockSize - blockSeek); } else { //Read count number of bytes memcpy(data,&blockData[blockSeek],count); //Update seek, free memory, return count bytes openFiles[fh].seekPos += count; free(blockData); return count; } } else { //Could not read block! free(blockData); return -1; } } //////////////////////////////////////////////////////////////////////////////// // // Function : hdd_write // Description : Writes count bytes of data buffer into block // // Inputs : File handle, data buffer, and count number of bytes to write // Outputs : Count number of written bytes if successful, -1 if unsuccessful // int32_t hdd_write(int16_t fh, void *data, int32_t count) { // Make sure file is open if (filledLocation[fh] == 0) { //File not open! return -1; } // Create a variable for the HDD send command and response. HddBitResp returnCommand = 0; int64_t result = 0; //Two cases: //Case 1: Block does not exist if (openFiles[fh].blockID == -1) { //Create block. Block size is count number of bytes, and 0 for ID, since it does not yet exist returnCommand = hdd_data_lane(generateHDDCommand(HDD_BLOCK_CREATE,count,0),data); //Next we need to isolate the result: result = returnCommand & ISOLATE_RESULT; result = result >> 32; if (result == -1) { //Could not create block return -1; } //Offset our data by the current seek position openFiles[fh].blockID = (int32_t)returnCommand; openFiles[fh].seekPos += count; //Case 2: Block has already been created } else { int64_t blockSize = hdd_read_block_size(openFiles[fh].blockID); int64_t fileSeek = openFiles[fh].seekPos; //Need a block-sized buffer to store the current block data char *blockData = malloc(blockSize); //Generate and send read command with block size and block ID. Read into our new buffer returnCommand = hdd_data_lane(generateHDDCommand(HDD_BLOCK_READ,blockSize,openFiles[fh].blockID),blockData); result = returnCommand & ISOLATE_RESULT; result = result >> 32; if (result == -1) { //Could not read! return -1; } if ((count+openFiles[fh].seekPos) > blockSize) { //Need a new buffer for all of the combined data char *newData = malloc(fileSeek + count); //Copy this block data into the new data block memcpy(newData, blockData, blockSize); //Finally, copy the count bytes of the old buffer into this new buffer memcpy (&newData[fileSeek],data,count); //Now, we create the new block with combined block size of count bytes + seek position returnCommand = hdd_data_lane(generateHDDCommand(HDD_BLOCK_CREATE,(count+fileSeek),0),newData); result = returnCommand & ISOLATE_RESULT; result = result >> 32; if (result == -1) { //Could not create block! return -1; } //Finally, delete the old block if(hdd_delete_block(openFiles[fh].blockID) == -1) { //Could not delete old block! return -1; } openFiles[fh].blockID = (int32_t)returnCommand; openFiles[fh].seekPos += count; free(blockData); free(newData); } else { //Current block size is large enough, but overwrite //will rewrite entire block. Need to prepare a new data buffer //Copy the data buffer to appropriate seek position in new buffer memcpy(&blockData[fileSeek],data,count); //Generate and send the overwrite command using block size and block ID, //using our newly updated block data buffer returnCommand = hdd_data_lane(generateHDDCommand(HDD_BLOCK_OVERWRITE,blockSize,openFiles[fh].blockID),blockData); //Next we need to isolate the result: result = returnCommand & ISOLATE_RESULT; result = result >> 32; free(blockData); if (result == -1) { //Overwrite error! return -1; } //Update seek openFiles[fh].seekPos += count; } } return count; } //////////////////////////////////////////////////////////////////////////////// // // Function : hdd_seek // Description : Moves the current seek position of an opened file to // position loc // // Inputs : File handle, new seek location // Outputs : -1 if out of range or file not open, 0 if successful // int32_t hdd_seek(int16_t fh, uint32_t loc) { //Make sure file is open if (filledLocation[fh] == 0) { //File not open! return -1; } if (loc > hdd_read_block_size(openFiles[fh].blockID)) { //Out of range!! return -1; } //Update seek position openFiles[fh].seekPos = loc; return 0; } //////////////////////////////////////////////////////////////////////////////// // // Function : hddIOUnitTest // Description : Perform a test of the HDD IO implementation // // Inputs : None // Outputs : 0 if successful or -1 if failure // // STUDENTS DO NOT MODIFY CODE BELOW UNLESS TOLD BY TA AND/OR INSTRUCTOR // int hddIOUnitTest(void) { // Local variables uint8_t ch; int16_t fh, i; int32_t cio_utest_length, cio_utest_position, count, bytes, expected; char *cio_utest_buffer, *tbuf; HDD_UNIT_TEST_TYPE cmd; char lstr[1024]; // Setup some operating buffers, zero out the mirrored file contents cio_utest_buffer = malloc(HDD_MAX_BLOCK_SIZE); tbuf = malloc(HDD_MAX_BLOCK_SIZE); memset(cio_utest_buffer, 0x0, HDD_MAX_BLOCK_SIZE); cio_utest_length = 0; cio_utest_position = 0; // Start by opening a file fh = hdd_open("temp_file.txt"); if (fh == -1) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : Failure open operation."); return(-1); } // Now do a bunch of operations for (i=0; i<HDD_IO_UNIT_TEST_ITERATIONS; i++) { // Pick a random sendCommand if (cio_utest_length == 0) { cmd = HIO_UNIT_TEST_WRITE; } else { cmd = getRandomValue(HIO_UNIT_TEST_READ, HIO_UNIT_TEST_SEEK); } // Execute the sendCommand switch (cmd) { case HIO_UNIT_TEST_READ: // read a random set of data count = getRandomValue(0, cio_utest_length); logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : read %d at position %d", count, cio_utest_position); bytes = hdd_read(fh, tbuf, count); if (bytes == -1) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : Read failure."); return(-1); } // Compare to what we expected if (cio_utest_position+count > cio_utest_length) { expected = cio_utest_length-cio_utest_position; } else { expected = count; } if (bytes != expected) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : short/long read of [%d!=%d]", bytes, expected); return(-1); } if ( (bytes > 0) && (memcmp(&cio_utest_buffer[cio_utest_position], tbuf, bytes)) ) { bufToString((unsigned char *)tbuf, bytes, (unsigned char *)lstr, 1024 ); logMessage(LOG_INFO_LEVEL, "HIO_UTEST R: %s", lstr); bufToString((unsigned char *)&cio_utest_buffer[cio_utest_position], bytes, (unsigned char *)lstr, 1024 ); logMessage(LOG_INFO_LEVEL, "HIO_UTEST U: %s", lstr); logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : read data mismatch (%d)", bytes); return(-1); } logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : read %d match", bytes); // update the position pointer cio_utest_position += bytes; break; case HIO_UNIT_TEST_APPEND: // Append data onto the end of the file // Create random block, check to make sure that the write is not too large ch = getRandomValue(0, 0xff); count = getRandomValue(1, HIO_UNIT_TEST_MAX_WRITE_SIZE); if (cio_utest_length+count < HDD_MAX_BLOCK_SIZE) { // Log, seek to end of file, create random value logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : append of %d bytes [%x]", count, ch); logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : seek to position %d", cio_utest_length); if (hdd_seek(fh, cio_utest_length)) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : seek failed [%d].", cio_utest_length); return(-1); } cio_utest_position = cio_utest_length; memset(&cio_utest_buffer[cio_utest_position], ch, count); // Now write bytes = hdd_write(fh, &cio_utest_buffer[cio_utest_position], count); if (bytes != count) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : append failed [%d].", count); return(-1); } cio_utest_length = cio_utest_position += bytes; } break; case HIO_UNIT_TEST_WRITE: // Write random block to the file ch = getRandomValue(0, 0xff); count = getRandomValue(1, HIO_UNIT_TEST_MAX_WRITE_SIZE); // Check to make sure that the write is not too large if (cio_utest_length+count < HDD_MAX_BLOCK_SIZE) { // Log the write, perform it logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : write of %d bytes [%x]", count, ch); memset(&cio_utest_buffer[cio_utest_position], ch, count); bytes = hdd_write(fh, &cio_utest_buffer[cio_utest_position], count); if (bytes!=count) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : write failed [%d].", count); return(-1); } cio_utest_position += bytes; if (cio_utest_position > cio_utest_length) { cio_utest_length = cio_utest_position; } } break; case HIO_UNIT_TEST_SEEK: count = getRandomValue(0, cio_utest_length); logMessage(LOG_INFO_LEVEL, "HDD_IO_UNIT_TEST : seek to position %d", count); if (hdd_seek(fh, count)) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : seek failed [%d].", count); return(-1); } cio_utest_position = count; break; default: // This should never happen CMPSC_ASSERT0(0, "HDD_IO_UNIT_TEST : illegal test sendCommand."); break; } } // Close the files and cleanup buffers, assert on failure if (hdd_close(fh)) { logMessage(LOG_ERROR_LEVEL, "HDD_IO_UNIT_TEST : Failure read comparison block.", fh); return(-1); } free(cio_utest_buffer); free(tbuf); // Return successfully return(0); }