diff --git a/src/raspberrypi/controllers/scsidev_ctrl.cpp b/src/raspberrypi/controllers/scsidev_ctrl.cpp index c6813c6..3df14b7 100644 --- a/src/raspberrypi/controllers/scsidev_ctrl.cpp +++ b/src/raspberrypi/controllers/scsidev_ctrl.cpp @@ -303,6 +303,11 @@ void FASTCALL SCSIDEV::Execute() CmdSeek6(); return; + // Unknown Nuvolink command + case 0x0C: + CmdNuvolink0C(); + return; + // INQUIRY case 0x12: CmdInquiry(); @@ -1117,6 +1122,38 @@ void FASTCALL SCSIDEV::CmdReadToc() DataIn(); } +//--------------------------------------------------------------------------- +// +// Unknown Vendor-specific command (probably cmmd_mdsens - Medium Sense) +// +//--------------------------------------------------------------------------- +void FASTCALL SCSIDEV::CmdNuvolink0C(){ + DWORD lun; + BOOL status; + + ASSERT(this); + + LOGTRACE("Received Medium Sense command for Nuvolink"); + + // Logical Unit + lun = (ctrl.cmd[1] >> 5) & 0x07; + if (!ctrl.unit[lun]) { + Error(); + return; + } + + // // Command processing on drive + // status = ctrl.unit[lun]->PlayAudio(ctrl.cmd); + // if (!status) { + // // Failure (Error) + // Error(); + // return; + // } + + // status phase + Status(); +} + //--------------------------------------------------------------------------- // // PLAY AUDIO(10) diff --git a/src/raspberrypi/controllers/scsidev_ctrl.h b/src/raspberrypi/controllers/scsidev_ctrl.h index f830b2d..f1b6be8 100644 --- a/src/raspberrypi/controllers/scsidev_ctrl.h +++ b/src/raspberrypi/controllers/scsidev_ctrl.h @@ -119,6 +119,8 @@ private: // GET MESSAGE(10) command void FASTCALL CmdSendMessage10(); // SEND MESSAGE(10) command + void FASTCALL CmdNuvolink0C(); + // SEND MESSAGE(10) command // データ転送 void FASTCALL Send(); diff --git a/src/raspberrypi/devices/scsi_nuvolink.cpp b/src/raspberrypi/devices/scsi_nuvolink.cpp new file mode 100644 index 0000000..195eae7 --- /dev/null +++ b/src/raspberrypi/devices/scsi_nuvolink.cpp @@ -0,0 +1,499 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ Emulation of the Nuvolink SC SCSI Ethernet interface ] +// +// This is based upon the fantastic documentation from Saybur on Gibhub +// for the scuznet device. He did most of the hard work of reverse +// engineering the protocol, documented here: +// https://github.com/saybur/scuznet/blob/master/PROTOCOL.md +// +// This does NOT include the file system functionality that is present +// in the Sharp X68000 host bridge. +// +// Note: This requires a the Nuvolink SC driver +//--------------------------------------------------------------------------- + +#include "scsi_nuvolink.h" + +//=========================================================================== +// +// SCSI Nuvolink SC Ethernet Adapter +// +//=========================================================================== +const char* SCSINuvolink::m_vendor_name = "Nuvotech"; +const char* SCSINuvolink::m_device_name = "NuvoSC"; +const char* SCSINuvolink::m_revision = "1.1r"; + + +//--------------------------------------------------------------------------- +// +// Constructor +// +//--------------------------------------------------------------------------- +SCSINuvolink::SCSINuvolink() : Disk() +{ + LOGTRACE("SCSINuvolink Constructor"); + // Nuvolink + disk.id = MAKEID('S', 'C', 'N', 'L'); + +#if defined(RASCSI) && defined(__linux__) && !defined(BAREMETAL) + // TAP Driver Generation + tap = new CTapDriver(); + m_bTapEnable = tap->Init(); + + // Generate MAC Address + memset(mac_addr, 0x00, 6); + if (m_bTapEnable) { + tap->GetMacAddr(mac_addr); + mac_addr[5]++; + } + + // Packet reception flag OFF + packet_enable = FALSE; +#endif // RASCSI && !BAREMETAL +} + +//--------------------------------------------------------------------------- +// +// Destructor +// +//--------------------------------------------------------------------------- +SCSINuvolink::~SCSINuvolink() +{ + LOGTRACE("SCSINuvolink Destructor"); +#if defined(RASCSI) && !defined(BAREMETAL) + // TAP driver release + if (tap) { + tap->Cleanup(); + delete tap; + } +#endif // RASCSI && !BAREMETAL +} + +//--------------------------------------------------------------------------- +// +// INQUIRY +// +//--------------------------------------------------------------------------- +int FASTCALL SCSINuvolink::Inquiry( + const DWORD *cdb, BYTE *buf, DWORD major, DWORD minor) +{ + char rev[32]; + int size; + WORD extended_size=0; + DWORD junk_data=0; + WORD junk_data_word=0; + + ASSERT(this); + ASSERT(cdb); + ASSERT(buf); + ASSERT(cdb[0] == 0x12); + + LOGTRACE("SCSINuvolink::Inquiry"); + LOGINFO("Inquiry with major %ld, minor %ld",major, minor); + + // The INQUIRY command from the Nuvolink can be 16 bits long + extended_size = (WORD)cdb[4] + (((WORD)cdb[3])<<8); + LOGINFO("Inquiry Size was %d", extended_size); + + // EVPD check + if (cdb[1] & 0x01) { + disk.code = DISK_INVALIDCDB; + return FALSE; + } + + /* The protocol documentaiton says that the size should never be + * greater than 292 */ + if(extended_size > 292) + { + LOGWARN("Extended size was greater than 292. Limiting to 292 instead of %d", extended_size); + extended_size = 292; + } + + // Clear the buffer + memset(buf, 0, extended_size); + + + // Basic data + // buf[0] ... Communication Device + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + buf[0] = 0x09; /* Communication device */ + + // SCSI-2 p.104 4.4.3 Incorrect logical unit handling + if (((cdb[1] >> 5) & 0x07) != disk.lun) { + buf[0] = 0x7f; + } + /* SCSI 2 device */ + buf[2] = 0x02; + /* SCSI 2 response type */ + buf[3] = 0x02; + /* No additional length */ + buf[4] = 0; + + // Vendor name + memcpy(&buf[8], m_vendor_name, strlen( m_vendor_name)); + + // Product name + memcpy(&buf[16], m_device_name, strlen(m_device_name)); + + // Revision + memcpy(&buf[32], m_revision, strlen(m_revision)); + + // MAC Address currently configured + memcpy(&buf[36], mac_addr, sizeof(mac_addr)); + + // Build-in Hardware MAC address + memcpy(&buf[36], mac_addr, sizeof(mac_addr)); + + // If this is an extended inquiry, add some additional details + if(extended_size >= 292) + { + // Header for SCSI statistics + buf[96] = 0x04; // Decimal 1234 + buf[97] = 0xD2; + + // Header for SCSI errors section + buf[184]=0x09; // Decimal 2345 + buf[185]=0x29; + + // Header for network statistics + buf[244]=0x0D; + buf[245]=0x80; + + // Received Packet Count + junk_data=100; + memcpy(&buf[246], &junk_data, sizeof(junk_data)); + + // Transmitted Packet Count + junk_data=200; + memcpy(&buf[250], &junk_data, sizeof(junk_data)); + + // Transmitted Request Count + junk_data=300; + memcpy(&buf[254], &junk_data, sizeof(junk_data)); + + // Reset Count + junk_data_word=50; + memcpy(&buf[258], &junk_data_word, sizeof(junk_data_word)); + + // Header for network errors + buf[260]=0x11; // Decimal 4567 + buf[261]=0xD7; + } + + // Success + disk.code = DISK_NOERROR; + return extended_size; +} + +//--------------------------------------------------------------------------- +// +// TEST UNIT READY +// +//--------------------------------------------------------------------------- +BOOL FASTCALL SCSINuvolink::TestUnitReady(const DWORD* /*cdb*/) +{ + ASSERT(this); + LOGTRACE("SCSINuvolink::TestUnitReady"); + + // TEST UNIT READY Success + disk.code = DISK_NOERROR; + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// GET MESSAGE(10) +// +//--------------------------------------------------------------------------- +int FASTCALL SCSINuvolink::GetMessage10(const DWORD *cdb, BYTE *buf) +{ + int type; + int phase; +#if defined(RASCSI) && !defined(BAREMETAL) + int func; + int total_len; + int i; +#endif // RASCSI && !BAREMETAL + + ASSERT(this); + LOGTRACE("SCSINuvolink::GetMessage10"); + + // Type + type = cdb[2]; + +#if defined(RASCSI) && !defined(BAREMETAL) + // Function number + func = cdb[3]; +#endif // RASCSI && !BAREMETAL + + // Phase + phase = cdb[9]; + + switch (type) { +#if defined(RASCSI) && !defined(BAREMETAL) + case 1: // Ethernet + // Do not process if TAP is invalid + if (!m_bTapEnable) { + return 0; + } + + switch (func) { + case 0: // Get MAC address + return GetMacAddr(buf); + + case 1: // Received packet acquisition (size/buffer) + if (phase == 0) { + // Get packet size + ReceivePacket(); + buf[0] = (BYTE)(packet_len >> 8); + buf[1] = (BYTE)packet_len; + return 2; + } else { + // Get package data + GetPacketBuf(buf); + return packet_len; + } + + case 2: // Received packet acquisition (size + buffer simultaneously) + ReceivePacket(); + buf[0] = (BYTE)(packet_len >> 8); + buf[1] = (BYTE)packet_len; + GetPacketBuf(&buf[2]); + return packet_len + 2; + + case 3: // Simultaneous acquisition of multiple packets (size + buffer simultaneously) + // Currently the maximum number of packets is 10 + // Isn't it too fast if I increase more? + total_len = 0; + for (i = 0; i < 10; i++) { + ReceivePacket(); + *buf++ = (BYTE)(packet_len >> 8); + *buf++ = (BYTE)packet_len; + total_len += 2; + if (packet_len == 0) + break; + GetPacketBuf(buf); + buf += packet_len; + total_len += packet_len; + } + return total_len; + } + break; +#endif // RASCSI && !BAREMETAL + + case 2: // Host Drive + // switch (phase) { + // case 0: // Get result code + // return ReadFsResult(buf); + + // case 1: // Return data acquisition + // return ReadFsOut(buf); + + // case 2: // Return additional data acquisition + // return ReadFsOpt(buf); + // } + break; + } + + // Error + ASSERT(FALSE); + return 0; +} + +//--------------------------------------------------------------------------- +// +// SEND MESSAGE(10) +// +//--------------------------------------------------------------------------- +BOOL FASTCALL SCSINuvolink::SendMessage10(const DWORD *cdb, BYTE *buf) +{ + int type; + int func; + int len; + + ASSERT(this); + ASSERT(cdb); + ASSERT(buf); + LOGTRACE("SCSINuvolink::SendMessage10"); + + // Type + type = cdb[2]; + + // Function number + func = cdb[3]; + + // Phase + // phase = cdb[9]; + + // Get the number of lights + len = cdb[6]; + len <<= 8; + len |= cdb[7]; + len <<= 8; + len |= cdb[8]; + + switch (type) { +#if defined(RASCSI) && !defined(BAREMETAL) + case 1: // Ethernet + // Do not process if TAP is invalid + if (!m_bTapEnable) { + return FALSE; + } + + switch (func) { + case 0: // MAC address setting + SetMacAddr(buf); + return TRUE; + + case 1: // Send packet + SendPacket(buf, len); + return TRUE; + } + break; +#endif // RASCSI && !BAREMETAL + + case 2: // Host drive + // switch (phase) { + // case 0: // issue command + // WriteFs(func, buf); + // return TRUE; + + // case 1: // additional data writing + // WriteFsOpt(buf, len); + // return TRUE; + // } + break; + } + + // Error + ASSERT(FALSE); + return FALSE; +} + +#if defined(RASCSI) && !defined(BAREMETAL) +//--------------------------------------------------------------------------- +// +// Get MAC Address +// +//--------------------------------------------------------------------------- +int FASTCALL SCSINuvolink::GetMacAddr(BYTE *mac) +{ + ASSERT(this); + ASSERT(mac); + LOGTRACE("SCSINuvolink::GetMacAddr"); + + memcpy(mac, mac_addr, 6); + return 6; +} + +//--------------------------------------------------------------------------- +// +// Set MAC Address +// +//--------------------------------------------------------------------------- +void FASTCALL SCSINuvolink::SetMacAddr(BYTE *mac) +{ + ASSERT(this); + ASSERT(mac); + LOGTRACE("SCSINuvolink::SetMacAddr"); + + memcpy(mac_addr, mac, 6); +} + +//--------------------------------------------------------------------------- +// +// Receive Packet +// +//--------------------------------------------------------------------------- +void FASTCALL SCSINuvolink::ReceivePacket() +{ + static const BYTE bcast_addr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + ASSERT(this); + ASSERT(tap); + LOGTRACE("SCSINuvolink::ReceivePacket"); + + // previous packet has not been received + if (packet_enable) { + return; + } + + // Receive packet + packet_len = tap->Rx(packet_buf); + + // Check if received packet + if (memcmp(packet_buf, mac_addr, 6) != 0) { + if (memcmp(packet_buf, bcast_addr, 6) != 0) { + packet_len = 0; + return; + } + } + + // Discard if it exceeds the buffer size + if (packet_len > 2048) { + packet_len = 0; + return; + } + + // Store in receive buffer + if (packet_len > 0) { + packet_enable = TRUE; + } +} + +//--------------------------------------------------------------------------- +// +// Get Packet +// +//--------------------------------------------------------------------------- +void FASTCALL SCSINuvolink::GetPacketBuf(BYTE *buf) +{ + int len; + + ASSERT(this); + ASSERT(tap); + ASSERT(buf); + LOGTRACE("SCSINuvolink::GetPacketBuf"); + + // Size limit + len = packet_len; + if (len > 2048) { + len = 2048; + } + + // Copy + memcpy(buf, packet_buf, len); + + // Received + packet_enable = FALSE; +} + +//--------------------------------------------------------------------------- +// +// Send Packet +// +//--------------------------------------------------------------------------- +void FASTCALL SCSINuvolink::SendPacket(BYTE *buf, int len) +{ + ASSERT(this); + ASSERT(tap); + ASSERT(buf); + LOGTRACE("SCSINuvolink::SendPacket"); + + tap->Tx(buf, len); +} +#endif // RASCSI && !BAREMETAL + diff --git a/src/raspberrypi/devices/scsi_nuvolink.h b/src/raspberrypi/devices/scsi_nuvolink.h new file mode 100644 index 0000000..591a7e3 --- /dev/null +++ b/src/raspberrypi/devices/scsi_nuvolink.h @@ -0,0 +1,102 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ Emulation of the Nuvolink SC SCSI Ethernet interface ] +// +// This is based upon the fantastic documentation from Saybur on Gibhub +// for the scuznet device. He did most of the hard work of reverse +// engineering the protocol, documented here: +// https://github.com/saybur/scuznet/blob/master/PROTOCOL.md +// +// This does NOT include the file system functionality that is present +// in the Sharp X68000 host bridge. +// +// Note: This requires a the Nuvolink SC driver +//--------------------------------------------------------------------------- +#pragma once + +#include "xm6.h" +#include "os.h" +#include "disk.h" +#include "ctapdriver.h" + +//=========================================================================== +// +// SCSI Nuvolink SC +// +//=========================================================================== +class SCSINuvolink : public Disk +{ +public: + // Basic Functions + SCSINuvolink(); + // Constructor + virtual ~SCSINuvolink(); + // Destructor + + // commands + int FASTCALL Inquiry(const DWORD *cdb, BYTE *buf, DWORD major, DWORD minor); + // INQUIRY command + BOOL FASTCALL TestUnitReady(const DWORD *cdb); + // TEST UNIT READY command + int FASTCALL GetMessage10(const DWORD *cdb, BYTE *buf); + // GET MESSAGE10 command + BOOL FASTCALL SendMessage10(const DWORD *cdb, BYTE *buf); + // SEND MESSAGE10 command + +private: + enum nuvolink_command_enum : BYTE { + cmmd_tstrdy = 0x00, // Test Unit Ready + cmmd_ethrst = 0x02, // Reset Statistics (Vendor Specific) + cmmd_rqsens = 0x03, // Request Sense + cmmd_ethwrt = 0x05, // Send Package (Vendor Specific) + cmmd_addr = 0x06, // Change MAC address (Vendor Specific) + cmmd_getmsg = 0x08, // Get Message + cmmd_mcast = 0x09, // Set Multicast Registers + cmmd_sndmsg = 0x0A, // Send Message + cmmd_mdsens = 0x0C, // Unknown Vendor Specific Command? + cmmd_inq = 0x12, // Inquiry + cmmd_rdiag = 0x1C, // Receive Diagnostic Results + cmmd_sdiag = 0x1D, // Send Diagnostic + }; + + static const char* m_vendor_name; + static const char* m_device_name; + static const char* m_revision; + +#if defined(RASCSI) && !defined(BAREMETAL) + int FASTCALL GetMacAddr(BYTE *buf); + // Get MAC address + void FASTCALL SetMacAddr(BYTE *buf); + // Set MAC address + void FASTCALL ReceivePacket(); + // Receive a packet + void FASTCALL GetPacketBuf(BYTE *buf); + // Get a packet + void FASTCALL SendPacket(BYTE *buf, int len); + // Send a packet + + CTapDriver *tap; + // TAP driver + BOOL m_bTapEnable; + // TAP valid flag + BYTE mac_addr[6]; + // MAC Addres + int packet_len; + // Receive packet size + BYTE packet_buf[0x1000]; + // Receive packet buffer + BOOL packet_enable; + // Received packet valid +#endif // RASCSI && !BAREMETAL + +}; diff --git a/src/raspberrypi/log.h b/src/raspberrypi/log.h index 76ff3fc..9514223 100644 --- a/src/raspberrypi/log.h +++ b/src/raspberrypi/log.h @@ -12,11 +12,70 @@ #if !defined(log_h) #define log_h +#include "spdlog/spdlog.h" +#include "spdlog/sinks/sink.h" + +#define SPDLOGWRAPPER(loglevel, ...)\ + do{ char buf[256]; \ + snprintf(buf, sizeof(buf),__VA_ARGS__); \ + spdlog::log(loglevel,buf);}while(0); + +#ifdef NDEBUG +// If we're doing a non-debug build, we want to skip the overhead of +// formatting the string, then calling the logger +#define LOGTRACE(...) ((void)0) +#define LOGDEBUG(...) ((void)0) +#else +#define LOGTRACE(...) SPDLOGWRAPPER(spdlog::level::trace, __VA_ARGS__) +#define LOGDEBUG(...) SPDLOGWRAPPER(spdlog::level::debug, __VA_ARGS__) +#endif +#define LOGINFO(...) SPDLOGWRAPPER(spdlog::level::info, __VA_ARGS__) +#define LOGWARN(...) SPDLOGWRAPPER(spdlog::level::warn, __VA_ARGS__) +#define LOGERROR(...) SPDLOGWRAPPER(spdlog::level::err, __VA_ARGS__) +#define LOGCRITICAL(...) SPDLOGWRAPPER(spdlog::level::critical, __VA_ARGS__) + + + + +/* +#define LOGERR(...) \ + if(should_log(spdlog::level::err)){\ + char buf[256]; \ + snprintf(buf, sizeof(buf),__VA_ARGS__); \ + spdlog::err(buf);} + +#define LOGWARN(...) \ + if(should_log(spdlog::level::warn)){\ + char buf[256]; \ + snprintf(buf, sizeof(buf),__VA_ARGS__); \ + spdlog::warn(buf);} + +#define LOGDEBUG(...) \ + if(should_log(spdlog::level::debug)){\ + char buf[256]; \ + snprintf(buf, sizeof(buf),__VA_ARGS__); \ + spdlog::debug(buf);} + -#define LOGINFO(...) \ - do{char buf[256]; snprintf(buf, 256,__VA_ARGS__); spdlog::info(buf);}while(0) #define LOGTRACE(...) \ - do{char buf[256]; snprintf(buf, 256,__VA_ARGS__); spdlog::trace(buf);}while(0) + if(should_log(spdlog::level::trace)){\ + char buf[256]; \ + snprintf(buf, sizeof(buf),__VA_ARGS__); \ + spdlog::trace(buf);} + +#define LOGERR(...) \ + do{char buf[256]; snprintf(buf, sizeof(buf),__VA_ARGS__); spdlog::error(buf);}while(0) +#define LOGWARN(...) \ + do{char buf[256]; snprintf(buf, sizeof(buf),__VA_ARGS__); spdlog::warn(buf);}while(0) +#define LOGINFO(...) \ + do{char buf[256]; snprintf(buf, sizeof(buf),__VA_ARGS__); spdlog::info(buf);}while(0) +#define LOGDEBUG(...) \ + do{char buf[256]; snprintf(buf, sizeof(buf),__VA_ARGS__); spdlog::debug(buf);}while(0) +#define LOGTRACE(...) \ + do{char buf[256]; char buf2[512]; snprintf(buf, sizeof(buf),__VA_ARGS__);\ + snprintf(buf2,sizeof(buf2),) + spdlog::trace(buf);}while(0) +*/ //=========================================================================== diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 82e04a5..7b34d30 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -21,6 +21,7 @@ #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/scsi_host_bridge.h" +#include "devices/scsi_nuvolink.h" #include "controllers/scsidev_ctrl.h" #include "controllers/sasidev_ctrl.h" #include "gpiobus.h" @@ -519,7 +520,7 @@ BOOL ProcessCmd(FILE *fp, int id, int un, int cmd, int type, char *file) pUnit = new SCSICD(); break; case 4: // BRIDGE - pUnit = new SCSIBR(); + pUnit = new SCSINuvolink(); break; default: FPRT(fp, "Error : Invalid device type\n"); @@ -908,6 +909,7 @@ bool ParseArgument(int argc, char* argv[]) //--------------------------------------------------------------------------- void FixCpu(int cpu) { + LOGCRITICAL("asdf"); cpu_set_t cpuset; int cpus; @@ -1065,8 +1067,18 @@ int main(int argc, char* argv[]) struct sched_param schparam; #endif // BAREMETAL + spdlog::set_level(spdlog::level::trace); - spdlog::trace("Entering the function with %d arguments", argc); +LOGTRACE("LOGTRACE") +LOGDEBUG("LOGDEBUG"); +LOGINFO("LOGINFO"); +LOGWARN("LOGWARN"); +LOGERROR("LOGERROR"); +LOGCRITICAL("LOGCRITICAL"); + + + + LOGTRACE("Entering the function with %d arguments", argc); // Output the Banner Banner(argc, argv); diff --git a/src/raspberrypi/rasctl.cpp b/src/raspberrypi/rasctl.cpp index 9141c23..d85fb69 100644 --- a/src/raspberrypi/rasctl.cpp +++ b/src/raspberrypi/rasctl.cpp @@ -10,6 +10,7 @@ //--------------------------------------------------------------------------- #include "os.h" +#include "log.h" //--------------------------------------------------------------------------- // @@ -83,6 +84,7 @@ int main(int argc, char* argv[]) // Display help if (argc < 2) { + LOGTRACE("Display help"); fprintf(stderr, "SCSI Target Emulator RaSCSI Controller\n"); fprintf(stderr, "Usage: %s -i ID [-u UNIT] [-c CMD] [-t TYPE] [-f FILE]\n",