// ********************************************************************
// VSPSerialPort - Serial port implementation
//
// Copyright © 2025 by EoF Software Labs
// Copyright © 2024 Apple Inc. (some copied parts)
// SPDX-License-Identifier: MIT
// ********************************************************************
// -- OS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// -- SerialDriverKit
#include
#include
#include
using namespace driverkit::serial;
// -- My
#include "VSPSerialPort.h"
#include "VSPController.h"
#include "VSPLogger.h"
#include "VSPDriver.h"
#define LOG_PREFIX "VSPSerialPort"
#define kVSPTTYBaseName "vsp"
#define kRxDataQueueName "rxDataQueue"
#ifndef IOLockFreeNULL
#define IOLockFreeNULL(l) { if (NULL != (l)) { IOLockFree(l); (l) = NULL; } }
#endif
#define VSPAquireLock(ivars) \
{ \
++ivars->m_lockLevel; \
VSPLog(LOG_PREFIX, "=> lock level=%d", ivars->m_lockLevel); \
IOLockLock(ivars->m_lock); \
}
#define VSPUnlock(ivars) \
{ \
VSPLog(LOG_PREFIX, "<= lock level=%d", ivars->m_lockLevel); \
--ivars->m_lockLevel; \
IOLockUnlock(ivars->m_lock); \
}
// Updated by SetModemStatus read by HwGetModemStatus
typedef struct {
bool cts;
bool dsr;
bool ri;
bool dcd;
} THwSerialStatus;
// Updated by HwProgramFlowControl
typedef struct{
uint32_t arg;
uint8_t xon;
uint8_t xoff;
} THwFlowControl;
// Updated by HwProgramMCR
typedef struct {
bool dtr;
bool rts;
} THwMCR;
// Updated by HwProgramUART and HwProgramBaudRate
typedef struct {
uint32_t baudRate;
uint8_t nDataBits;
uint8_t nHalfStopBits;
uint8_t parity;
} TUartParameters;
// Updated by RxError and HwSendBreak
typedef struct {
bool overrun;
bool gotBreak;
bool framingError;
bool parityError;
} TErrorState;
typedef struct {
uint8_t* buffer;
uint32_t length;
} TRXBufferState;
// Driver instance state resource
struct VSPSerialPort_IVars {
IOService* m_provider = nullptr;
VSPDriver* m_parent = nullptr; // VSPDriver instance
uint8_t m_portId = 0; // port id given by VSPDriver
uint8_t m_portLinkId = 0; // port link id given by VSPDriver
IOLock* m_lock = nullptr; // for resource locking
volatile atomic_int m_lockLevel = 0;
/* OS provided memory descriptors by overridden
* method ConnectQueues(...) */
SerialPortInterface* m_spi; // OS serial port interface
IOBufferMemoryDescriptor* m_txqbmd; // VSP TX queue memory descriptor
IOAddressSegment m_txseg = {}; // VSP TX buffer segment
IOBufferMemoryDescriptor* m_rxqbmd; // VSP RX queue memory descriptor
IOAddressSegment m_rxseg = {}; // VSP RX buffer segment
TRXBufferState m_rxstate = {}; // RX dequeue data
// Timed events
IODispatchQueue* m_tiQueue = nullptr;
IOTimerDispatchSource* m_tiSource = nullptr;
OSAction* m_tiAction = nullptr;
OSData* m_tiData = nullptr;
// Response buffer created by TxAvailable()
IODispatchQueue* m_rxQueue = nullptr;
IODataQueueDispatchSource* m_rxSource = nullptr;
OSAction* m_rxAction = nullptr;
// Serial interface
TErrorState m_errorState = {};
TUartParameters m_uartParams = {};
THwSerialStatus m_hwStatus = {};
THwFlowControl m_hwFlowControl = {};
THwMCR m_hwMCR = {};
uint32_t m_hwLatency = 25;
bool m_txNextOffset = 0;
bool m_txIsComplete = false;
bool m_rxIsComplete = false;
bool m_hwActivated = false;
};
// --------------------------------------------------------------------
// Allocate internal resources
//
bool VSPSerialPort::init(void)
{
bool result;
VSPLog(LOG_PREFIX, "init called.\n");
if (!(result = super::init())) {
VSPLog(LOG_PREFIX, "super::init falsed. result=%d\n", result);
goto error_exit;
}
// Create instance state resource
ivars = IONewZero(VSPSerialPort_IVars, 1);
if (!ivars) {
VSPLog(LOG_PREFIX, "Unable to allocate driver data.\n");
result = false;
goto error_exit;
}
VSPLog(LOG_PREFIX, "init finished.\n");
return true;
error_exit:
return result;
}
// --------------------------------------------------------------------
// Release internal resources
//
void VSPSerialPort::free(void)
{
VSPLog(LOG_PREFIX, "free called.\n");
// Release instance state resource
IOSafeDeleteNULL(ivars, VSPSerialPort_IVars, 1);
super::free();
}
// --------------------------------------------------------------------
// Start_Impl(IOService* provider)
//
kern_return_t IMPL(VSPSerialPort, Start)
{
kern_return_t ret;
VSPLog(LOG_PREFIX, "Start: called.\n");
// sane check our driver instance vars
if (!ivars) {
VSPLog(LOG_PREFIX, "Start: Private driver instance is NULL\n");
return kIOReturnInvalid;
}
// call super method (apple style)
ret = Start(provider, SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "Start(super): failed. code=%d\n", ret);
return ret;
}
// remember OS provider
ivars->m_provider = provider;
// the resource locker
ivars->m_lock = IOLockAlloc();
if (ivars->m_lock == nullptr) {
VSPLog(LOG_PREFIX, "Start: Unable to allocate lock object.\n");
goto error_exit;
}
// create our own TTY name and index
if ((ret = setupTTYBaseName()) != kIOReturnSuccess) {
goto error_exit;
}
// default UART parameters
ivars->m_uartParams.baudRate = 112500;
ivars->m_uartParams.nHalfStopBits = 2;
ivars->m_uartParams.nDataBits = 8;
ivars->m_uartParams.parity = 0;
VSPLog(LOG_PREFIX, "Start: Allocate timer queue and resources.\n");
ret = IODispatchQueue::Create("kVSPTimerQueue", 0, 0, &ivars->m_tiQueue);
if (ret != kIOReturnSuccess || !ivars->m_tiQueue) {
VSPLog(LOG_PREFIX, "Start: Unable to create timer queue. code=%d\n", ret);
goto error_exit;
}
ret = IOTimerDispatchSource::Create(ivars->m_tiQueue, &ivars->m_tiSource);
if (ret != kIOReturnSuccess || !ivars->m_tiSource) {
VSPLog(LOG_PREFIX, "Start: Unable to create timer queue. code=%d\n", ret);
goto error_exit;
}
ret = CreateActionNotifyRXReady(sizeof(ivars->m_tiData), &ivars->m_tiAction);
if (ret != kIOReturnSuccess || !ivars->m_tiAction) {
VSPLog(LOG_PREFIX, "Start: Unable to timer callback action. code=%d\n", ret);
goto error_exit;
}
ret = ivars->m_tiSource->SetHandler(ivars->m_tiAction);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "Start: Unable to assign timer action. code=%d\n", ret);
goto error_exit;
}
VSPLog(LOG_PREFIX, "Start: register service.\n");
// Register driver instance to IOReg
if ((ret = RegisterService()) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "Start: RegisterService failed. code=%d\n", ret);
goto error_exit;
}
VSPLog(LOG_PREFIX, "Start: driver started successfully.\n");
return kIOReturnSuccess;
error_exit:
cleanupResources();
Stop(provider, SUPERDISPATCH);
return ret;
}
// --------------------------------------------------------------------
// Stop_Impl(IOService* provider)
//
kern_return_t IMPL(VSPSerialPort, Stop)
{
kern_return_t ret;
VSPLog(LOG_PREFIX, "Stop called.\n");
// Unlink VSP parent
ivars->m_parent = nullptr;
// Remove all IVars resources
cleanupResources();
/* Shutdown instane */
if ((ret= Stop(provider, SUPERDISPATCH)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::Stop failed. code=%d\n", ret);
} else {
VSPLog(LOG_PREFIX, "driver successfully removed.\n");
}
return ret;
}
// --------------------------------------------------------------------
// Remove all resources in IVars
//
void VSPSerialPort::cleanupResources()
{
VSPLog(LOG_PREFIX, "cleanupResources called.\n");
OSSafeReleaseNULL(ivars->m_tiQueue);
OSSafeReleaseNULL(ivars->m_tiSource);
OSSafeReleaseNULL(ivars->m_tiAction);
OSSafeReleaseNULL(ivars->m_tiData);
IOLockFreeNULL(ivars->m_lock);
}
// ====================================================================
// ** ----------------[ Connection live cycle ]--------------------- **
// ====================================================================
// --------------------------------------------------------------------
// ConnectQueues_Impl( ... )
// First call
kern_return_t IMPL(VSPSerialPort, ConnectQueues)
{
IOAddressSegment ifseg = {};
size_t txcapacity, rxcapacity;
IOReturn ret;
VSPLog(LOG_PREFIX, "ConnectQueues called\n");
//-- Sane check --//
if (!in_txqlogsz || !in_rxqlogsz) {
VSPLog(LOG_PREFIX, "ConnectQueues: Invalid in_rxqlogsz or in_txqlogsz detected.\n");
return kIOReturnBadArgument;
}
// Lock to ensure thread safety
VSPAquireLock(ivars);
// Convert the base-2 logarithmic size of the buffer or the in_txqmd parameter.
txcapacity = (size_t) ::pow(2, in_txqlogsz);
ret = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionIn, txcapacity, 0, &ivars->m_txqbmd);
if (ret != kIOReturnSuccess || !ivars->m_txqbmd) {
VSPLog(LOG_PREFIX, "Start: Unable to create TX memory descriptor. code=%d\n", ret);
goto error_exit;
}
// Convert the base-2 logarithmic size of the buffer for the in_rxqmd parameter.
rxcapacity = (size_t) ::pow(2, in_rxqlogsz);
ret = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionOut, rxcapacity, 0, &ivars->m_rxqbmd);
if (ret != kIOReturnSuccess || !ivars->m_rxqbmd) {
VSPLog(LOG_PREFIX, "Start: Unable to create RX memory descriptor. code=%d\n", ret);
goto error_exit;
}
// make sure the parameters are zero
in_rxqoffset = 0;
in_txqoffset = 0;
// Call super to get SerialPortInterface and set our RX/TX memory descriptors
ret = ConnectQueues(ifmd, rxqmd, txqmd,
ivars->m_rxqbmd,
ivars->m_txqbmd,
in_rxqoffset,
in_txqoffset,
in_rxqlogsz,
in_txqlogsz, SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::ConnectQueues failed. code=%d\n", ret);
goto error_exit;
}
//-- Sane check --//
if (!ifmd || !(*ifmd) || !txqmd || !(*txqmd) || !rxqmd || !(*rxqmd)) {
VSPLog(LOG_PREFIX, "ConnectQueues: Invalid memory descriptors detected. (NULL)\n");
ret = kIOReturnBadArgument;
goto error_exit;
}
if ((*txqmd) != ivars->m_txqbmd) {
VSPLog(LOG_PREFIX, "ConnectQueues: Invalid 'txqmd' memory descriptor detected.\n");
ret = kIOReturnInvalid;
goto error_exit;
}
if ((*rxqmd) != ivars->m_rxqbmd) {
VSPLog(LOG_PREFIX, "ConnectQueues: Invalid 'rxqmd' memory descriptor detected.\n");
ret = kIOReturnInvalid;
goto error_exit;
}
// Get the address of the TX memory descriptor
ret = ivars->m_txqbmd->GetAddressRange(&ivars->m_txseg);
if (ret != kIOReturnSuccess || !ivars->m_txseg.address || ivars->m_txseg.length != txcapacity) {
VSPLog(LOG_PREFIX, "ConnectQueues: Unable to get TX-MD segment. code=%d\n", ret);
goto error_exit;
}
// Get the address of the RX memory descriptor
ret = ivars->m_rxqbmd->GetAddressRange(&ivars->m_rxseg);
if (ret != kIOReturnSuccess || !ivars->m_rxseg.address || ivars->m_rxseg.length != rxcapacity) {
VSPLog(LOG_PREFIX, "ConnectQueues: Unable to get TX-MD segment. code=%d\n", ret);
goto error_exit;
}
// Get the address of the IOSerialPortInterface segment (mapped space)
if ((ret = (*ifmd)->GetAddressRange(&ifseg)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "ConnectQueues: IF GetAddressRange failed. code=%d\n", ret);
goto error_exit;
}
// Initialize the indexes
ivars->m_spi = reinterpret_cast(ifseg.address);
ivars->m_spi->txCI = 0;
ivars->m_spi->txPI = 0;
// -- Setup RX response queue and dispatch source --
// 0 = No options are currently defined.
// 0 = No priorities are currently defined.
ret = IODispatchQueue::Create(kRxDataQueueName, 0, 0, &ivars->m_rxQueue);
if (ret != kIOReturnSuccess || !ivars->m_rxQueue) {
VSPLog(LOG_PREFIX, "ConnectQueues: Unable to create RX queue. code=%d\n", ret);
goto error_exit;
}
ret = IODataQueueDispatchSource::Create(ivars->m_rxseg.length, ivars->m_rxQueue, &ivars->m_rxSource);
if (ret != kIOReturnSuccess || !ivars->m_rxSource) {
VSPLog(LOG_PREFIX, "ConnectQueues: Unable to creade dispatch soure. code=%d\n", ret);
goto error_exit;
}
// Async notification from IODataQueueDispatchSource::DataAvailable
ret = CreateActionRxEchoAsyncEvent(ivars->m_rxseg.length, &ivars->m_rxAction);
if (ret != kIOReturnSuccess || !ivars->m_rxAction) {
VSPLog(LOG_PREFIX, "ConnectQueues: Unable to create RX action. code=%d\n", ret);
goto error_exit;
}
// Set async async callback action
ret = ivars->m_rxSource->SetDataAvailableHandler(ivars->m_rxAction);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "ConnectQueues: SetDataAvailableHandler failed. code=%d\n", ret);
goto error_exit;
}
// Modem is ready
ivars->m_hwStatus.dcd = true;
ivars->m_hwStatus.cts = true;
VSPUnlock(ivars);
return kIOReturnSuccess;
error_exit:
OSSafeReleaseNULL(ivars->m_txqbmd);
OSSafeReleaseNULL(ivars->m_rxqbmd);
OSSafeReleaseNULL(ivars->m_rxAction);
OSSafeReleaseNULL(ivars->m_rxSource);
OSSafeReleaseNULL(ivars->m_rxQueue);
VSPUnlock(ivars);
return ret;
}
// --------------------------------------------------------------------
// DisconnectQueues_Impl()
// Last call
kern_return_t IMPL(VSPSerialPort, DisconnectQueues)
{
IOReturn ret;
VSPLog(LOG_PREFIX, "DisconnectQueues called\n");
// Lock to ensure thread safety
VSPAquireLock(ivars);
// stop RX dispatch queue
OSSafeReleaseNULL(ivars->m_rxSource);
OSSafeReleaseNULL(ivars->m_rxAction);
OSSafeReleaseNULL(ivars->m_rxQueue);
// reset SPI pointer from OS
ivars->m_spi = nullptr;
// Remove our memory descriptors
OSSafeReleaseNULL(ivars->m_txqbmd);
OSSafeReleaseNULL(ivars->m_rxqbmd);
// reset our TX/RX segments
ivars->m_txseg = {};
ivars->m_rxseg = {};
// Reset modem status
ivars->m_hwStatus.dcd = false;
ivars->m_hwStatus.dsr = false;
ivars->m_hwStatus.cts = false;
// Unlock thread
VSPUnlock(ivars);
ret = DisconnectQueues(SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::DisconnectQueues: failed. code=%d\n", ret);
return ret;
}
return ret;
}
// --------------------------------------------------------------------
// NotifyRXReady_Impl(OSAction* action)
// Called by timer dispatch queue source
void IMPL(VSPSerialPort, NotifyRXReady)
{
VSPLog(LOG_PREFIX, "NotifyRXReady called.\n");
// Make sure action object is valid
if (!action) {
VSPLog(LOG_PREFIX, "NotifyRXReady bad argument. action=0%llx\n", (uint64_t) action);
return;
}
// Notify IOUserSerial rx data ready to dispatch
this->RxDataAvailable_Impl();
}
// --------------------------------------------------------------------
// RxEchoAsyncEvent_Impl(OSAction* action)
// Called by RX data available dispatch queue source
void IMPL(VSPSerialPort, RxEchoAsyncEvent)
{
IODataQueueDispatchSource* source;
IOReturn ret;
// Lock to ensure thread safety
VSPAquireLock(ivars);
// skip if complete...
// RxEchoAsyncEvent is called twice by OS
if (!ivars->m_rxSource->IsDataAvailable()) {
//VSPLog(LOG_PREFIX, "RxEchoAsyncEvent RX source is empty, done.\n");
goto finish;
}
VSPLog(LOG_PREFIX, "++++++++++++++++++++++++++++++++++++++++\n");
VSPLog(LOG_PREFIX, "RxEchoAsyncEvent: called.\n");
// Make sure action object is valid
if (!action) {
VSPLog(LOG_PREFIX, "RxEchoAsyncEvent bad argument. action=0%llx\n", (uint64_t) action);
goto finish;
}
// This port is assigned to a port link?
if (ivars->m_portLinkId) {
// source for other port instance
source = ivars->m_rxSource;
// sendToPortLink lock also
VSPUnlock(ivars);
// send to other port instance
if ((ret = sendToPortLink(source)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "RxEchoAsyncEvent: Data routing failed. code=%d\n", ret);
}
return;
}
// set my own RX source
source = ivars->m_rxSource;
// sendResponse lock also
VSPUnlock(ivars);
// echo to my self...
sendResponse(source);
// no double unlock :)
return;
finish:
VSPUnlock(ivars);
}
// --------------------------------------------------------------------
// TxDataAvailable_Impl()
// TX data ready to read from m_txqbmd segment
void IMPL(VSPSerialPort, TxDataAvailable)
{
IOReturn ret;
uint8_t* buffer;
uint64_t address;
uint32_t size;
VSPLog(LOG_PREFIX, "--------------------------------------------------\n");
VSPLog(LOG_PREFIX, "TxDataAvailable called.\n");
// Lock to ensure thread safety
VSPAquireLock(ivars);
// Reset first
ivars->m_txIsComplete = false;
// We working...
ivars->m_hwStatus.cts = false;
ivars->m_hwStatus.dsr = false;
// Show me indexes be fore manipulate
VSPLog(LOG_PREFIX, "TxDataAvailable: [IOSPI-TX 1] txPI: %d, txCI: %d, txqoffset: %d, txqlogsz: %d",
ivars->m_spi->txPI, ivars->m_spi->txCI, ivars->m_spi->txqoffset, ivars->m_spi->txqlogsz);
// skip if nothing to do
if (!ivars->m_spi->txPI) {
VSPLog(LOG_PREFIX, "TxDataAvailable: spi->txPI is zero, skip\n");
goto finish;
}
// Get address of new TX data position
address = ivars->m_txseg.address + ivars->m_spi->txCI;
buffer = reinterpret_cast(address);
// Calculate available data in TX buffer
size = ivars->m_spi->txPI - ivars->m_spi->txCI;
#ifdef DEBUG // !! Debug ....
VSPLog(LOG_PREFIX, "TxDataAvailable: dump buffer=0x%llx len=%u\n", (uint64_t) buffer, size);
for (uint64_t i = 0; i < size; i++) {
VSPLog(LOG_PREFIX, "TxDataAvailable: buffer[%02lld]=0x%02x %c\n", i, buffer[i], buffer[i]);
}
#endif
// Loopback TX data by async response event
if ((ret = this->enqueueResponse(buffer, size)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "TxDataAvailable: Unable to enqueue response. code=%d\n", ret);
goto finish;
}
// TX -> RX echo done
ivars->m_txIsComplete = true;
// We reserve 1K size from the capacity from t_txqbmd.
// This protects against a buffer overflow.
if (((uint32_t) ivars->m_txseg.length - ivars->m_spi->txPI) < 1024) {
ivars->m_spi->txPI = 0;
ivars->m_spi->txCI = 0;
}
// Reset TX consumer index to end of received block. This increases the
// offset for the m_txqbmd buffer. Finally the DEXT crash if to protection.
// Reset values like txPI = 0 and txCI = 0 after some transmissions.
else {
ivars->m_spi->txCI = ivars->m_spi->txPI;
}
// Show me indexes be fore manipulation
VSPLog(LOG_PREFIX, "TxDataAvailable: [IOSPI-TX 2] txPI: %d, txCI: %d, txqoffset: %d, txqlogsz: %d",
ivars->m_spi->txPI, ivars->m_spi->txCI, ivars->m_spi->txqoffset, ivars->m_spi->txqlogsz);
// reset memory
memset(buffer, 0, size);
VSPLog(LOG_PREFIX, "TxDataAvailable complete.\n");
finish:
VSPUnlock(ivars);
}
// --------------------------------------------------------------------
// Enque given buffer in RX dispatch source and raise async event
// Enqueue given buffer into RX dispatch queue source
kern_return_t VSPSerialPort::enqueueResponse(void* buffer, uint64_t size)
{
IOReturn ret = 0;
VSPLog(LOG_PREFIX, "enqueueResponse called.\n");
// Make sure everything is fine
if (!ivars || !ivars->m_rxSource || !buffer || !size) {
VSPLog(LOG_PREFIX, "enqueueResponse: Invalid arguments\n");
return kIOReturnBadArgument;
}
// Disable dispatching on RX queue source
if ((ret = ivars->m_rxSource->SetEnable(false)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "enqueueResponse: RX source disable failed. code=%d\n", ret);
return ret;
}
// Response by adding queue entry to RX queue source
ret = ivars->m_rxSource->Enqueue((uint32_t) size, ^(void *data, size_t dataSize) {
VSPLog(LOG_PREFIX, "enqueueResponse: enqueue data=0x%llx size=%lld\n", (uint64_t) data, size);
if (dataSize < size) {
memset(data, 0xff, dataSize);
return;
}
memset(data, 0, size);
memcpy(data, buffer, size);
});
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "enqueueResponse: RX enqueue failed. code=%d\n", ret);
switch (ret) {
case kIOReturnOverrun:
VSPLog(LOG_PREFIX, "enqueueResponse: ^^-> overrun\n");
break;
case kIOReturnError:
VSPLog(LOG_PREFIX, "enqueueResponse: ^^-> corrupt\n");
break;
}
// Leave dispatch disabled (??)
return ret;
}
// Enable dispatching on RX queue source
if ((ret = ivars->m_rxSource->SetEnable(true)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "enqueueResponse: RX source enable failed. code=%d\n", ret);
return ret;
}
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// RxDataAvailable_Impl()
// Notify OS response RX data ready for client
void IMPL(VSPSerialPort, RxDataAvailable)
{
VSPLog(LOG_PREFIX, "RxDataAvailable called.\n");
RxDataAvailable(SUPERDISPATCH);
}
// --------------------------------------------------------------------
// RxFreeSpaceAvailable_Impl()
// Notification to this instance that RX buffer space is available for
// your device’s data
void IMPL(VSPSerialPort, RxFreeSpaceAvailable)
{
VSPLog(LOG_PREFIX, "RxFreeSpaceAvailable called.\n");
RxFreeSpaceAvailable(SUPERDISPATCH);
}
// --------------------------------------------------------------------
// TxFreeSpaceAvailable_Impl()
// Notify OS ready for more client data
void IMPL(VSPSerialPort, TxFreeSpaceAvailable)
{
VSPLog(LOG_PREFIX, "TxFreeSpaceAvailable called.\n");
TxFreeSpaceAvailable(SUPERDISPATCH);
}
// --------------------------------------------------------------------
// SetModemStatus_Impl(bool cts, bool dsr, bool ri, bool dcd)
// Called during serial port setup or communication
kern_return_t IMPL(VSPSerialPort, SetModemStatus)
{
IOReturn ret;
VSPLog(LOG_PREFIX, "SetModemStatus called [in] CTS=%d DSR=%d RI=%d DCD=%d\n",
cts, dsr, ri, dcd);
VSPAquireLock(ivars);
ivars->m_hwStatus.cts = cts;
ivars->m_hwStatus.dsr = dsr;
ivars->m_hwStatus.ri = ri;
ivars->m_hwStatus.dcd = dcd;
VSPUnlock(ivars);
ret = SetModemStatus(cts, dsr, ri, dcd, SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::SetModemStatus failed. code=%d\n", ret);
return ret;
}
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// RxError_Impl(bool overrun, bool break, bool framing, bool parity)
// Called on given error states
kern_return_t IMPL(VSPSerialPort, RxError)
{
kern_return_t ret;
VSPLog(LOG_PREFIX, "RxError called.\n");
if (overrun) {
VSPLog(LOG_PREFIX, "RX overrun.\n");
}
if (gotBreak) {
VSPLog(LOG_PREFIX, "RX got break.\n");
}
if (framingError) {
VSPLog(LOG_PREFIX, "RX framing error.\n");
}
if (parityError) {
VSPLog(LOG_PREFIX, "RX parity error.\n");
}
VSPAquireLock(ivars);
ivars->m_errorState.overrun = overrun;
ivars->m_errorState.framingError = framingError;
ivars->m_errorState.gotBreak = gotBreak;
ivars->m_errorState.parityError = parityError;
VSPUnlock(ivars);
ret = RxError(overrun, gotBreak, framingError, parityError, SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::RxError: failed. code=%d\n", ret);
return ret;
}
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwActivate_Impl()
// Called after ConnectQueues() or other reasons
kern_return_t IMPL(VSPSerialPort, HwActivate)
{
kern_return_t ret = kIOReturnIOError;
VSPLog(LOG_PREFIX, "HwActivate called.\n");
VSPAquireLock(ivars);
ivars->m_hwActivated = true;
// ???
ivars->m_hwStatus.dcd = true;
// ???
ivars->m_hwStatus.cts = true;
VSPUnlock(ivars);
ret = HwActivate(SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::HwActivate failed. code=%d\n", ret);
return ret;
}
return ret;
}
// --------------------------------------------------------------------
// HwDeactivate_Impl()
// Called before DisconnectQueues() or other reasons
kern_return_t IMPL(VSPSerialPort, HwDeactivate)
{
kern_return_t ret = kIOReturnIOError;
VSPLog(LOG_PREFIX, "HwDeactivate called.\n");
VSPAquireLock(ivars);
ivars->m_hwActivated = false;
// ???
ivars->m_hwStatus.dcd = false;
// ???
ivars->m_hwStatus.cts = false;
VSPUnlock(ivars);
ret = HwDeactivate(SUPERDISPATCH);
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "super::HwDeactivate failed. code=%d\n", ret);
return ret;
}
return ret;
}
// --------------------------------------------------------------------
// HwResetFIFO_Impl()
// Called by client to TxFreeSpaceAvailable or RxFreeSpaceAvailable
// or other reasons.
kern_return_t IMPL(VSPSerialPort, HwResetFIFO)
{
VSPLog(LOG_PREFIX, "HwResetFIFO called -> tx=%d rx=%d\n",
tx, rx);
VSPAquireLock(ivars);
// ?? notify caller (IOUserSerial)
if (rx) {
ivars->m_hwStatus.dsr = false;
}
// ?? notify caller (IOUserSerial)
if (tx) {
ivars->m_hwStatus.cts = true;
}
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwSendBreak_Impl()
// Called during client communication life cycle
kern_return_t IMPL(VSPSerialPort, HwSendBreak)
{
VSPLog(LOG_PREFIX, "HwSendBreak called -> sendBreak=%d\n", sendBreak);
VSPAquireLock(ivars);
ivars->m_errorState.gotBreak = sendBreak;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwGetModemStatus_Impl()
// Called during client communication life cycle
kern_return_t IMPL(VSPSerialPort, HwGetModemStatus)
{
VSPLog(LOG_PREFIX, "HwGetModemStatus called [out] CTS=%d DSR=%d RI=%d DCD=%d\n", //
ivars->m_hwStatus.cts, ivars->m_hwStatus.dsr, //
ivars->m_hwStatus.ri, ivars->m_hwStatus.dcd);
VSPAquireLock(ivars);
if (cts != nullptr) {
(*cts) = ivars->m_hwStatus.cts;
}
if (dsr != nullptr) {
(*dsr) = ivars->m_hwStatus.dsr;
}
if (ri != nullptr) {
(*ri) = ivars->m_hwStatus.ri;
}
if (dcd != nullptr) {
(*dcd) = ivars->m_hwStatus.dcd;
}
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwProgramUART_Impl()
// Called during serial port setup or other reason
kern_return_t IMPL(VSPSerialPort, HwProgramUART)
{
VSPLog(LOG_PREFIX, "HwProgramUART called -> baudRate=%d "
"nDataBits=%d nHalfStopBits=%d parity=%d\n",
baudRate, nDataBits, nHalfStopBits, parity);
VSPAquireLock(ivars);
ivars->m_uartParams.baudRate = baudRate;
ivars->m_uartParams.nDataBits = nDataBits;
ivars->m_uartParams.nHalfStopBits = nHalfStopBits;
ivars->m_uartParams.parity = parity;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwProgramBaudRate_Impl()
// Called during serial port setup or other reason
kern_return_t IMPL(VSPSerialPort, HwProgramBaudRate)
{
VSPLog(LOG_PREFIX, "HwProgramBaudRate called -> baudRate=%d\n", baudRate);
VSPAquireLock(ivars);
ivars->m_uartParams.baudRate = baudRate;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwProgramMCR_Impl()
// Called during serial port setup or other reason
kern_return_t IMPL(VSPSerialPort, HwProgramMCR)
{
VSPLog(LOG_PREFIX, "HwProgramMCR called -> DTR=%d RTS=%d\n",
dtr, rts);
VSPAquireLock(ivars);
ivars->m_hwMCR.dtr = dtr;
ivars->m_hwMCR.rts = rts;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwProgramLatencyTimer_Impl()
// Called during serial port setup or other reason
kern_return_t IMPL(VSPSerialPort, HwProgramLatencyTimer)
{
VSPLog(LOG_PREFIX, "HwProgramLatencyTimer called -> latency=%d\n",
latency);
VSPAquireLock(ivars);
ivars->m_hwLatency = latency;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// HwProgramLatencyTimer_Impl()
// Called during serial port setup or other reason
kern_return_t IMPL(VSPSerialPort, HwProgramFlowControl)
{
VSPLog(LOG_PREFIX, "HwProgramFlowControl called -> arg=%02x xon=%02x xoff=%02x\n",
arg, xon, xoff);
VSPAquireLock(ivars);
ivars->m_hwFlowControl.arg = arg;
ivars->m_hwFlowControl.xon = xon;
ivars->m_hwFlowControl.xoff = xoff;
VSPUnlock(ivars);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// Called by VSPDriver instance to link to parent level
//
void VSPSerialPort::setParent(VSPDriver* parent)
{
VSPLog(LOG_PREFIX, "setParent called.\n");
if (ivars != nullptr && !ivars->m_parent) {
ivars->m_parent = parent;
}
}
// --------------------------------------------------------------------
// Remove link to VSPDriver instance
//
void VSPSerialPort::unlinkParent()
{
VSPLog(LOG_PREFIX, "unlinkParent called.\n");
ivars->m_parent = nullptr;
}
// --------------------------------------------------------------------
// Set the serial port identifier
//
void VSPSerialPort::setPortIdentifier(uint8_t id)
{
VSPLog(LOG_PREFIX, "setPortIdentifier id=%d.\n", id);
ivars->m_portId = id;
}
// --------------------------------------------------------------------
// Get the serial port identifier
//
uint8_t VSPSerialPort::getPortIdentifier()
{
return ivars->m_portId;
}
// --------------------------------------------------------------------
// Set the serial port link identifier
//
void VSPSerialPort::setPortLinkIdentifier(uint8_t id)
{
VSPLog(LOG_PREFIX, "setPortLinkIdentifier id=%d.\n", id);
ivars->m_portLinkId = id;
}
// --------------------------------------------------------------------
// Get the serial port link identifier
//
uint8_t VSPSerialPort::getPortLinkIdentifier()
{
return ivars->m_portLinkId;
}
// --------------------------------------------------------------------
// Called by VSPDriver instance to set TTY base and number based on
// managed instance of this object instance
kern_return_t VSPSerialPort::setupTTYBaseName()
{
IOReturn ret;
OSDictionary* properties = nullptr;
OSString* baseName = nullptr;
VSPLog(LOG_PREFIX, "setupTTYBaseName called.\n");
// setup custom TTY name
if ((ret = CopyProperties(&properties)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "setupTTYBaseName: Unable to get properties. code=%d\n", ret);
return ret;
}
//CreateNameMatchingDictionary(OSString *serviceName, OSDictionary *matching)
//baseName = OSString::withCString(kVSPTTYBaseName);
//properties->setObject(kIOTTYBaseNameKey, baseName);
// write back to driver instance
if ((ret = SetProperties(properties)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "setupTTYBaseName: Unable to set TTY base name. code=%d\n", ret);
//return ret; // ??? an error - why???
}
OSSafeReleaseNULL(baseName);
OSSafeReleaseNULL(properties);
return kIOReturnSuccess;
}
// --------------------------------------------------------------------
// Called by RxEchoAsyncEvent in case of 'port is linked' state
//
kern_return_t VSPSerialPort::sendToPortLink(IODataQueueDispatchSource* source)
{
IOReturn ret;
VSPSerialPort* port = nullptr;
TVSPPortLinkItem* item = nullptr;
VSPLog(LOG_PREFIX, "sendToPortLink called.\n");
VSPAquireLock(ivars);
void* link;
uint8_t id = ivars->m_portLinkId;
if ((ret = ivars->m_parent->getPortLinkById(id, &link)) || !link) {
VSPLog(LOG_PREFIX, "sendToPortLink: Parent getPortLinkById failed. code=%d\n", ret);
goto finish;
}
item = reinterpret_cast(link);
VSPLog(LOG_PREFIX, "sendToPortLink: got linkId=%d\n", item->id);
if (item->sourcePort.id != ivars->m_portId) {
port = item->sourcePort.port;
}
else if (item->targetPort.id != ivars->m_portId) {
port = item->targetPort.port;
} else {
VSPLog(LOG_PREFIX, "sendToPortLink: Double port IDs detectd! myLinkId=%d myPortId=%d\n",
id, ivars->m_portId);
ret = kIOReturnInvalid;
goto finish;
}
if (!port) {
VSPLog(LOG_PREFIX, "sendToPortLink: Linked port NULL pointer! myLinkId=%d myPortId=%d\n",
id, ivars->m_portId);
ret = kIOReturnInvalid;
goto finish;
}
VSPLog(LOG_PREFIX, "sendToPortLink: got portId=%d\n", port->getPortIdentifier());
VSPUnlock(ivars);
return port->sendResponse(source);
finish:
VSPUnlock(ivars);
return ret;
}
// --------------------------------------------------------------------
// Called by other port instance or RxEchoAsyncEvent
//
kern_return_t VSPSerialPort::sendResponse(IODataQueueDispatchSource* source)
{
IOReturn ret;
uint64_t address;
if (!source) {
VSPLog(LOG_PREFIX, "sendResponse: Invalid argument.\n");
return kIOReturnBadArgument;
}
if (!ivars->m_spi || !ivars->m_rxqbmd) {
VSPLog(LOG_PREFIX, "sendResponse: Device closed.\n");
return kIOReturnNotOpen;
}
VSPAquireLock(ivars);
// Lock RX dispatch queue source
if ((ret = ivars->m_rxSource->SetEnable(false)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "sendResponse: RX source SetEnable false failed. code=%d\n", ret);
goto finish;
}
// Update modem status
ivars->m_hwStatus.cts = false;
ivars->m_hwStatus.dsr = false;
VSPLog(LOG_PREFIX, "sendResponse: [IOSPI-RX 1] rxPI=%d rxCI=%d rxqoffset=%d rxqlogsz=%d\n",
ivars->m_spi->rxPI, ivars->m_spi->rxCI, ivars->m_spi->rxqoffset, ivars->m_spi->rxqlogsz);
// reset [!! 1 !!]
// We start always our response from beginning
// of the memory descriptor buffer
ivars->m_spi->rxPI = 0;
ivars->m_spi->rxCI = 0;
// Get address to the RX ring buffer
address = ivars->m_rxseg.address + ivars->m_spi->rxPI;
ivars->m_rxstate.buffer = reinterpret_cast(address);
ivars->m_rxstate.length = 0;
VSPLog(LOG_PREFIX, "sendResponse: dequeue RX source\n");
// Remove queue entry from RX queue source
ret = ivars->m_rxSource->Dequeue(^(const void *data, size_t dataSize) {
VSPLog(LOG_PREFIX, "sendResponse: dequeue data=0x%llx size=%ld\n", (uint64_t) data, dataSize);
// Copy data from RX queue source to RX-MD buffer
memcpy(ivars->m_rxstate.buffer, data, dataSize);
// Save transfered data size
ivars->m_rxstate.length = (uint32_t) dataSize;
});
if (ret != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "sendResponse: RX dequeue failed. code=%d\n", ret);
switch (ret) {
case kIOReturnUnderrun:
VSPLog(LOG_PREFIX, "sendResponse: ^^-> underrun\n");
break;
case kIOReturnError:
VSPLog(LOG_PREFIX, "sendResponse: ^^-> corrupt\n");
break;
}
goto finish;
}
// Notify queue entry has been removed
ivars->m_rxSource->SendDataServiced();
#ifdef DEBUG // !! Debug ....
VSPLog(LOG_PREFIX, "sendResponse: Dump m_rxqmd buffer=0x%llx size=%u\n",
(uint64_t) ivars->m_rxstate.buffer, ivars->m_rxstate.length);
for (uint64_t i = 0; i < ivars->m_rxstate.length; i++) {
VSPLog(LOG_PREFIX, "sendResponse: buffer[%02lld]=0x%02x %c\n", i,
ivars->m_rxstate.buffer[i], ivars->m_rxstate.buffer[i]);
}
#endif
// Update RX producer index
ivars->m_spi->rxPI = ivars->m_rxstate.length;
VSPLog(LOG_PREFIX, "sendResponse: [IOSPI-RX 2] rxPI=%d rxCI=%d rxqoffset=%d rxqlogsz=%d\n",
ivars->m_spi->rxPI, ivars->m_spi->rxCI, ivars->m_spi->rxqoffset, ivars->m_spi->rxqlogsz);
// Notify OS interrest parties
this->RxDataAvailable_Impl();
// Update modem status
ivars->m_hwStatus.cts = true;
ivars->m_hwStatus.dsr = true;
// Unlock RX queue source
if ((ret = ivars->m_rxSource->SetEnable(true)) != kIOReturnSuccess) {
VSPLog(LOG_PREFIX, "sendResponse: RX source SetEnable true failed. code=%d\n", ret);
}
VSPLog(LOG_PREFIX, "sendResponse: complete.\n");
finish:
VSPUnlock(ivars);
return ret;
}
// Notify RX complete to OS interrest parties
// using client defined latency time
//ret = ivars->m_tiSource->WakeAtTime(
// kIOTimerClockMonotonicRaw,
// clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)
// + (ivars->m_hwLatency * 1000000),
// 1000000000);
//if (ret != kIOReturnSuccess) {
// VSPLog(LOG_PREFIX, "RxEchoAsyncEvent: tiSource WakeAtTime failed. code=%d\n", ret);
// goto finish;
//}