added more Net code still WIP, included Tom palettes, hasty alteration to 3D model editor slider to allow higher precision.

This commit is contained in:
TomAwezome 2020-03-27 14:06:16 -04:00
parent c7a04c4ee7
commit 7e6e442e0d
31 changed files with 2665 additions and 144 deletions

Binary file not shown.

0
src/Home/MakeHome.CC Normal file → Executable file
View File

View File

@ -56,6 +56,7 @@ I64 ARPSend(U16 operation,
{//method currently assumes send_ and target_ip EndianU16 already...
U8* ethernet_frame;
CARPHeader *header;
I64 de_index = EthernetFrameAllocate(&ethernet_frame,
send_mac_address,
dest_mac_address,
@ -63,7 +64,7 @@ I64 ARPSend(U16 operation,
sizeof(CARPHeader));
if (de_index < 0) return de_index;// error state
CARPHeader *header = ethernet_frame;
header = ethernet_frame;
header->hardware_type = EndianU16(HTYPE_ETHERNET);
header->protocol_type = EndianU16(ETHERTYPE_IPV4);
@ -83,6 +84,9 @@ I64 ARPSend(U16 operation,
return 0;
}
CARPHash *ARPCacheFindByIP(U32 ip_address)
{
U8 *ip_string = MStrPrint("%d", ip_address);
@ -183,9 +187,10 @@ I64 ARPHandler(CEthernetFrame *ethernet_frame)
if (header->target_protocol_addr == arp_globals.local_ipv4)
ARPSend(ARP_REPLY,
header->sender_hardware_addr,
EthernetGetMAC,
EthernetGetMAC(),
arp_globals.local_ipv4,
header->sender_hardware_addr, header->sender_protocol_addr);
header->sender_hardware_addr,
header->sender_protocol_addr);
break;
case ARP_REPLY:
ARPCachePut(EndianU32(header->sender_protocol_addr), header->sender_hardware_addr);

View File

@ -36,7 +36,7 @@ NetQueue
Ethernet
EthernetFrameParse (Needs refining!)
EthernetFrameParse (has a fixme)
ARP
@ -48,14 +48,31 @@ ARP
ARPSetIPV4Address
Sockets
Sockets (just finite state modifiers.)
SocketStateErr
SocketAccept
SocketClose
SocketBind
SocketConnect
SocketListen
SocketReceive
SocketReceiveFrom
SocketSend
SocketSendTo
IPV4
IPV4Checksum
GetMACAddressForIP
IPV4PacketAllocate
IPV4PacketFinish (alias for EthernetFrameFinish)
IPV4GetAddress
IPV4SetAddress
IPV4SetSubnet
IPV4ParsePacket
ICMP
ICMPSendReply
ICMPHandler
TCP
@ -69,5 +86,6 @@ DNS
NetHandlerTask
NetHandlerTask
HandleNetQueueEntry (4 million context swaps per second!)
IPV4Handler
NetConfig

View File

@ -11,6 +11,8 @@ Departures from Shrine:
Many file global vars have been condensed into global classes.
Sockets are non-standard.
Stack progress: (# done, ~ WIP, . N/A)
# PCNet-II Driver
@ -18,21 +20,27 @@ Stack progress: (# done, ~ WIP, . N/A)
# NetQueue
- no NetHandler currently
~ Ethernet
- need frame parse method
# Ethernet
- double check.
# ARP (Address Resolution Protocol)
- double check.
. Sockets
- thinking I should nix Sockets and
work out a STREAMS protocol instead.
Need more docs on STREAMS.
~ Sockets
- Implemented a Finite State Machine
through Socket function calls.
Sockets themselves do nothing,
all calls simply do/don't alter
socket state. Protocols will
need to detect socket states
and respond appropriately.
. IPV4 (Internet Protocol version 4)
# IPV4 (Internet Protocol version 4)
- double check, some FIXMEs.
. ICMP (Internet Control Message Protocol)
# ICMP (Internet Control Message Protocol)
- ? has FIXMEs.
. TCP (Transmission Control Protocol)
@ -53,4 +61,13 @@ Stack progress: (# done, ~ WIP, . N/A)
made so we can just do a switch (ethertype) and
then directly call the appropriate handler.
- As of ICMP, this seems fine to do. IPV4 Handler needed
to do something very similar to what NetHandler does,
so for simplicity IPV4Handler was moved to NetHandler
file, as it does a switch with protocol types that
are defined in files after IPV4.CC is included. This should
likely be the convention for Handlers that need to process
data from an all-knowing perspective (i.e. NetHandler file is
included last.)
. NetConfig

Binary file not shown.

182
src/Home/Net/Docs/SocketNotes.DD Executable file
View File

@ -0,0 +1,182 @@
Sockets... not planning to conform to unix-y socket standards unless absolutely required.
All over socket code is these global vars, lowercase functions.. its a mess.
Shrine does some pretty gnarly stuff to do their NativeSockets.
They have a class, called a CSocket, which is just a bunch of function pointers.
Then, they have another, called CSocketClass, which is a single direction list of
a domain, a type, some padding (...), and a pointer to a CSocket..
I don't even want to get into the CAddrResolver which is a yet another
function pointer to some resolver function.
Then, the socketclass and addrresolver are made into globals!
When they try to find a socket class, they have to loop through all
of the defined socket classes until they find one that matches the
params of domain and type..
Since UDP is regarded apparently as more simple than TCP,
looking at Shrine's UDP code gives a little insight into
what's going on without getting too caught up in high
level intricacies.
When UDP registers their socketclass , they pass in the #defined
type and domain, then pass in the socketclass itself.
All the typical socket functions as defined in shrine,
basically just take in this socketclass and then redirect
execution to the function defined in the socketclass.
At first, I thought "oh, why not just make the args a class!" but
this doesn't address the need to store functions as members, which
is a little niggerlicious still.
So I need a better way to do these socket function calls, without
having to rely on a pointer magic backend. The immediate thought
I have, is to ditch general use socket functions and enforce
application specific function definitions. In a way, that might
kinda end up meaning that a hell of a lot of the other way
of doing things would be stripped.
Maybe, analyze what passed variables are manipulated, and hardcode
functions with higher specificity.
...
/* Zenith Sockets are non-standard.
----------------------
Shrine implementation:
in_addr
sockaddr
sockaddr_in
addrinfo
inet_aton
inet_ntoa
socket
accept
close
bind
connect
listen
recv
recvfrom
send
sendto
setsockopt
getaddrinfo
freeaddrinfo
AddrInfoCopy
gai_strerror
create_connection
RegisterSocketClass
----------------------
Zenith implementation:
----------------------
*/
/* I think a class for a socket is a good idea.
But i do not think that the class should have
function pointers.
A CSocket is literally just a list of function pointers.
In Shrine, for example, the only UDP socket functions
that actually do anything are UDPSocketBind, UDPSocketClose,
UDPSocketRecvFrom, UDPSocketSendTo, and UDPSocketSetSockopt.
The rest just make the compiler happy by saying no_warn on the
args ...
If not function pointers, how would it work?
STREAMS / XTI / TPI lookin sexy rn ngl haha.
Maybe a hybrid of the alternatives and sockets.
One thought is to reserve sockets as some unique class thing,
and have functions that take args that go to a switch statement
to determine which code to next execute.
Perhaps, two Socket related files would make more sense,
one which defines some low-level things, and another that
(like NetHandler) is all-knowing and would discern based
on a switch statement which socket-related functions to run.
If doing this, must make sure that UDP/TCP/etc won't
need to know things only the SocketHandler or whatever we'd
call that would know.
At the root of ShrineSockets, all sockets made in later
files must be put into a RegisterSocketClass call,
which keeps track of which functions go where based on
the searched-for domain and type. So, I'd think a
beginning to an unfuck would be, if it ends up needed,
using a hash table, maybe the key would be (domain << 64 | type)
as a string, assuming the args are still I64.
I fight and I fight.. these bold ideas might be forced
to take place only after I've un-fucked sockets code alone.
Then maybe after massive unfucking/refucking, it can be more
quickly discerened what the better way to go from there is.
*/
//at the end of the day , one distinction MUST be made...
//What IS a socket ? ...
....
---------------------
Thoughts.
Sense in SocketNextState(CSocket *socket, U8 state) ?
Sense in having two socket states, current and requested?
With one, if we try to modify the value, we modify it.
There's no way of conveying or interacting with
higher-defined (TCP, UDP, etc) socket functions.
With a current and requested state, we would be
able to show both what the socket is doing right
now, and what the user/code has requested the
socket to change to. Say, there is a failure
in the higher level code when it sees a socket
and its requested next state: it will process
appropriate higher-level code and then ask for
another Socket State change accordingly or something.

Binary file not shown.

View File

@ -0,0 +1,43 @@
$FG,0$So. We don't have any way to REMOVE or POP treenodes or treequeues.
Goal?
- CUDPTreeNode* UDPTreeNodePop (args?)
- U0 UDPTreeNodeFree(CUDPTreeNode *node)
- CUDPTreeQueue* UDPTreeNodeQueuePop (args?)
- U0 UDPTreeNodeQueueFree(CUDPTreeQueue *queue)
Pop will give you the now-popped-out node pointer.
Free will take that popped node and Free its resources from mem.
To POP a node, different steps necessary.
If node has no left/right, it's simple. Just free it all.
If node has only left or only right, pop it by storing its
pointer for returning, then altering the left/right pointers
for the above tree, and below tree, to stitch it one link closer
If node has both a left and right branch...
Well, i dunno. harder to stitch because conflicting trees.
Hail-mary approach is to take all the branches and respective
branches and just filter back into tree with Add.
It sounds painful.
Is it feasible to just take the Whole sub-Trees of a node,
cut ties between node and tree, then add back in the left,
then right sub trees? If we're getting rid of a central node,
that splits off into higher or lowers... ultimately, if we sever
the original node, either direction is still going to replace
the position of where the original node was, because it will
still be higher/lower than the higher-up-node. Adding back in
nodes would just leave the tree a little funky-lookin.

Binary file not shown.

View File

@ -29,7 +29,7 @@ U0 EthernetInitGlobals()
//This method is retarded. It needs help.
//todo: check length , figure out the length+4
U0 EthernetFrameParse(CEthernetFrame *frame_out, U8 *frame, U16 length)
{
//Shrine has a FIXME for check length! We need to figure out what
@ -38,15 +38,15 @@ U0 EthernetFrameParse(CEthernetFrame *frame_out, U8 *frame, U16 length)
//of the current system should be done with less extra allocation
//altogether, more passing.
MemCopy(frame_out->destination_address, frame, 6); // 6 ? Why 6? Need a #define...
MemCopy(frame_out->destination_address, frame, MAC_ADDRESS_LENGTH);
MemCopy(frame_out->source_address, frame + 6, 6); // Ridiculous, what are those
MemCopy(frame_out->source_address, frame + MAC_ADDRESS_LENGTH, MAC_ADDRESS_LENGTH);
frame_out->ethertype = frame[13] | (frame[12] << 8); // This would be readable with #defines
frame_out->ethertype = frame[ETHERNET_ETHERTYPE_OFFSET+1] | (frame[ETHERNET_ETHERTYPE_OFFSET] << 8);
frame_out->data = frame + 14; // Fuck you Shrine
frame_out->data = frame + ETHERNET_DATA_OFFSET;
frame_out->length = length - 14 + 4; //... He has a comment literally just saying "??". Wow.
frame_out->length = length - ETHERNET_MAC_HEADER_LENGTH + 4; //... He has a comment literally just saying "??". Wow.
}
EthernetInitGlobals;

83
src/Home/Net/ICMP.CC Executable file
View File

@ -0,0 +1,83 @@
#include "IPV4"
#define ICMP_TYPE_ECHO_REPLY 0
#define ICMP_TYPE_ECHO_REQUEST 8
class CICMPHeader // Shrine's use of id and seq indicate this header is for Address Mask Reply/Request
{
U8 type;
U8 code;
U16 checksum;
U16 identifier;
U16 sequence_number;
};
I64 ICMPSendReply(U32 destination_ip_address,
U16 identifier,
U16 sequence_number,
U16 request_checksum,
U8 *payload,
I64 length)
{
U8 *frame;
I64 de_index;
CICMPHeader *header;
de_index = IPV4PacketAllocate(&frame,
IP_PROTOCOL_ICMP,
IPV4GetAddress(),
destination_ip_address,
sizeof(CICMPHeader) + length);
if (de_index < 0)
{
ZenithLog("ICMP Send Reply failed to allocate IPV4 packet.\n");
return de_index;
}
header = frame;
header->type = ICMP_TYPE_ECHO_REPLY;
header->code = 0; // why is 0 okay?
header->checksum = EndianU16(EndianU16(request_checksum) + 0x0800);// this is awful. Shrine says hack alert.
header->identifier = identifier;
header->sequence_number = sequence_number;
MemCopy(frame + sizeof(CICMPHeader), payload, length);
IPV4PacketFinish(de_index);
//return IPV4PacketFinish
}
I64 ICMPHandler(CIPV4Packet *packet)
{
CICMPHeader *header;
if (packet->length < sizeof(CICMPHeader))
{
ZenithLog("ICMP Handler caught wrong IPV4 length.\n");
return -1;
}
header = packet->data;
if (header->type == ICMP_TYPE_ECHO_REQUEST && header->code == 0)//why zero? need more #defines in here
{
ARPCachePut(packet->source_ip_address, packet->ethernet_frame->source_address);
ICMPSendReply(packet->source_ip_address,
header->identifier,
header->sequence_number,
header->checksum,
packet->data + sizeof(CICMPHeader),
packet->length - sizeof(CICMPHeader)); // wut
}
return 0;
}

324
src/Home/Net/IPV4.CC Executable file
View File

@ -0,0 +1,324 @@
#include "ARP"
#define IPV4_ERR_ADDR_INVALID -200001
#define IPV4_ERR_HOST_UNREACHABLE -200002
//i'd like to know why 64 was chosen
#define IPV4_TTL 64
//Look up IP Protocol Numbers online to see many more.
#define IP_PROTOCOL_ICMP 0x01
#define IP_PROTOCOL_TCP 0x06
#define IP_PROTOCOL_UDP 0x11
class CIPV4Packet
{
CEthernetFrame *ethernet_frame;
U32 source_ip_address;
U32 destination_ip_address;
U8 protocol;
U8 padding[7];
U8 *data;
I64 length;
};
class CIPV4Header
{ // note: U4's in some U8s.
U8 version_ihl; //Version for IPV4 is 4. IHL=Internet Header Length
U8 dscp_ecn; // DSCP=Differentiated Services Code Point. ECN=Explicit Congestion Notification
U16 total_length; // min 20B max 65535
U16 identification;
U16 flags_fragment_offset; // flags first(?) 3 bits. fragment offset min 0 max 65528
// flag: bit 0: reserved must be 0. bit 1: don't fragment. bit 2: more fragments
U8 time_to_live; // specified in seconds, wikipedia says nowadays serves as a hop count
U8 protocol;
U16 header_checksum;
U32 source_ip_address;
U32 destination_ip_address;
}
class CIPV4Globals
{ // _be indicates Big Endian
U32 local_ip;
U32 local_ip_be;
U32 ipv4_router_address;
U32 ipv4_subnet_mask;
} ipv4_globals;
U0 InitIPV4Globals()
{
ipv4_globals.local_ip = 0;
ipv4_globals.local_ip_be = 0;
ipv4_globals.ipv4_router_address = 0;
ipv4_globals.ipv4_subnet_mask = 0;
}
// For now, trusting Shrine's implement
// of this. Shrine links back to
// http://stackoverflow.com/q/26774761/2524350
U16 IPV4Checksum(U8* header, I64 length)
{ //todo. make names clearer, and better comments.
I64 nleft = length;
U16 *w = header;
I64 sum = 0;
while (nleft > 1)
{
sum += *(w++);
nleft -= 2;
}
// "mop up an odd byte, if necessary"
if (nleft == 1)
{
sum += ((*w) & 0x00FF);
}
// "add back carry outs from top 16 bits to low 16 bits"
sum = (sum >> 16) + (sum & 0xFFFF); // "add hi 16 to low 16"
sum += (sum >> 16); // add carry
return (~sum) & 0xFFFF;
}
I64 GetMACAddressForIP(U32 ip_address, U8 **mac_out)
{
CARPHash *entry;
I64 retries;
I64 attempt;
/*
switch (ip_address)
{
case 0:
return IPV4_ERR_ADDR_INVALID;
case 0xFFFFFFFF:
*mac_out = ethernet_globals.ethernet_broadcast;
return 0;
}
*/
if (ip_address == 0)
{
ZenithLog("Get MAC for IP failed. Address = 0\n");
return IPV4_ERR_ADDR_INVALID;
}
if (ip_address == 0xFFFFFFFF)
{
ZenithLog("Get MAC for IP requested and returning ethernet broadcast\n");
*mac_out = ethernet_globals.ethernet_broadcast;
return 0;
}
// "outside this subnet; needs routing"
if ((ip_address & ipv4_globals.ipv4_subnet_mask) !=
(ipv4_globals.local_ip & ipv4_globals.ipv4_subnet_mask))
{
// Shrine recurses here... and says FIXME infinite loop if mis-configured...
}
else // "local network"
{
entry = ARPCacheFindByIP(ip_address);
if (entry)
{
*mac_out = entry->mac_address;
return 0;
}
//else, not in cache, need to request it
// "Up to 4 retries, 500 ms each"
retries = 4;
while (retries)
{
attempt = 0;
for (attempt = 0; attempt < 50; attempt++)
{
Sleep(10);
entry = ARPCacheFindByIP(ip_address);
if (entry) break;
}
if (entry)
{
*mac_out = entry->mac_address;
return 0;
}
retries--;
}
//Shrine does some in_addr mess to log error
ZenithLog("Failed to resolve address %d",ip_address);
return IPV4_ERR_HOST_UNREACHABLE;
}
}
I64 IPV4PacketAllocate(U8 **frame_out,
U8 protocol,
U32 source_ip_address,
U32 destination_ip_address,
I64 length)
{
U8 *ethernet_frame;
U8 *destination_mac_address;
I64 error;
I64 de_index;
I64 internet_header_length;
CIPV4Header *header;
error = GetMACAddressForIP(destination_ip_address, &destination_mac_address);
if (error < 0)
{
ZenithLog("IPV4 Packet Allocate failed to get MAC for destination.\n");
return error;
}
de_index = EthernetFrameAllocate(&ethernet_frame,
EthernetGetMAC(),
destination_mac_address,
ETHERTYPE_IPV4,
sizeof(CIPV4Header) + length);
if (de_index < 0)
{
ZenithLog("IPV4 Ethernet Frame Allocate failed.\n");
return de_index;
}
internet_header_length = 5;// ... why. need a #define
header = ethernet_frame;
header->version_ihl = internet_header_length | (4 << 4);// whaaat the fuck is this? #define!
header->dscp_ecn = 0; // a clear define of what this actually means would be good
header->total_length = EndianU16(internet_header_length * 4 + length); //...why?
header->identification = 0;// define would be clearer
header->flags_fragment_offset = 0; // define would be clearer
header->time_to_live = IPV4_TTL;
header->protocol = protocol;
header->header_checksum = 0; // why is 0 ok?
header->source_ip_address = EndianU32(source_ip_address);
header->destination_ip_address = EndianU32(destination_ip_address);
header->header_checksum = IPV4Checksum(header, internet_header_length + 4);//why the 4's...
*frame_out = ethernet_frame + sizeof(CIPV4Header);
return de_index;
}
U0 IPV4PacketFinish(I64 de_index) //alias for EthernetFrameFinish
{
EthernetFrameFinish(de_index);
}
U32 IPV4GetAddress()
{
return ipv4_globals.local_ip;
}
U0 IPV4SetAddress(U32 ip_address)
{
ipv4_globals.local_ip = ip_address;
ipv4_globals.local_ip_be = EndianU32(ip_address);
ARPSetIPV4Address(ip_address);
}
U0 IPV4SetSubnet(U32 router_address, U32 subnet_mask)
{
ipv4_globals.ipv4_router_address = router_address;
ipv4_globals.ipv4_subnet_mask = subnet_mask;
}
//I64
U0 IPV4ParsePacket(CIPV4Packet *packet_out, CEthernetFrame *ethernet_frame)
{
//...if ethertype not ipv4 error?
//Shrine says FIXME check ethernet_frame length !! ... we need to know what's appropriate
CIPV4Header *header = ethernet_frame->data;
I64 header_length = (header->version_ihl & 0x0F) * 4;//this Has to go. at least abstract or something..
U16 total_length = EndianU16(header->total_length);
packet_out->ethernet_frame = ethernet_frame;
packet_out->source_ip_address = EndianU32(header->source_ip_address);
packet_out->destination_ip_address = EndianU32(header->destination_ip_address);
packet_out->protocol = header->protocol;
packet_out->data = ethernet_frame->data + header_length;
packet_out->length = total_length - header_length;
// return 0;
}
// IPV4 handler moved to NetHandlerTask file.
InitIPV4Globals;

View File

@ -1,3 +1,23 @@
U0 IPV4Handler(CEthernetFrame *ethernet_frame)
{
CIPV4Packet packet;
IPV4ParsePacket(&packet, ethernet_frame);
ARPCachePut(packet.source_ip_address, ethernet_frame->source_address);
switch (packet.protocol)
{
case IP_PROTOCOL_ICMP:
ICMPHandler(&packet);
break;
case IP_PROTOCOL_TCP:
break;
case IP_PROTOCOL_UDP:
break;
}
}
U0 HandleNetQueueEntry(CNetQueueEntry *entry)
{
@ -10,6 +30,9 @@ U0 HandleNetQueueEntry(CNetQueueEntry *entry)
case ETHERTYPE_ARP:
ARPHandler(&ethernet_frame);
break;
case ETHERTYPE_IPV4:
IPV4Handler(&ethernet_frame);
break;
}
}
@ -25,7 +48,8 @@ U0 NetHandlerTask(I64)
}
else
{
LBts(&Fs->task_flags, TASKf_SUSPENDED);
LBts(&Fs->task_flags, TASKf_IDLE);
//ZenithLog("IDLE: NetHandler\n");
Yield;
}

View File

@ -1,7 +1,11 @@
//Shrine mentions possibly using two FIFOs (in our case, Queues) for pending and empty frames.
//If logical to implement, perhaps Zenith NetQueue code should do something similar to that idea.
/* Shrine mentions possibly using two FIFOs
(in our case, Queues) for pending and
empty frames. If logical to implement,
perhaps Zenith NetQueue code should
do something similar to that idea. */
//Each Ethernet Frame will be represented as an entry in a CQueue.
/* Each Ethernet Frame will be represented
as an entry in a CQueue. */
class CNetQueueEntry:CQueue
{
I64 length; //todo: change to packet_length?
@ -16,17 +20,21 @@ class CNetQueueEntry:CQueue
itself, the Head. */
CQueue *net_queue; // no QueueRemove the Head! only Entries!
//Net Handler Task is set idle and active depending on if entries in Net Queue. See NetHandlerTask.CC
/* Net Handler Task is set idle and active depending
on if entries in Net Queue. See NetHandlerTask.CC */
CTask *net_handler_task = NULL;
U0 NetQueueInit()
{
net_queue = CAlloc(sizeof(CQueue));
QueueInit(net_queue);
}
CNetQueueEntry *NetQueuePull()
{//Returns a pointer to a CNetQueueEntry, or NULL pointer if Net Queue is empty.
{/* Returns a pointer to a CNetQueueEntry,
or NULL pointer if Net Queue is empty. */
CNetQueueEntry *entry;
if (net_queue->next != net_queue)
@ -35,11 +43,15 @@ CNetQueueEntry *NetQueuePull()
QueueRemove(entry);
}
else // Queue is empty if head->next is head itself.
{
entry = NULL;
}
return entry;
}
U0 NetQueuePushCopy(U8 *data, I64 length)
{/* Pushes a copy of the packet data and length
into the Net Queue. The NetQueueEntry is inserted
@ -53,8 +65,9 @@ U0 NetQueuePushCopy(U8 *data, I64 length)
QueueInsert(entry, net_queue->last);
//Set Net Handler Task active.
ZenithLog("ACTIVE: NetHandler\n");
if (net_handler_task)
LBtr(&net_handler_task->task_flags, TASKf_SUSPENDED);
LBtr(&net_handler_task->task_flags, TASKf_IDLE);
}

View File

@ -13,8 +13,8 @@
- Clear documentation.
*/
#include "Net/Net.HH"
#include "Net/NetQueue"
#include "Net.HH"
#include "NetQueue"
#define PCNET_DEVICE_ID 0x2000
#define PCNET_VENDOR_ID 0x1022
@ -68,8 +68,8 @@
#define PCNET_CTRL_STOP 2
#define PCNET_CTRL_RINT 10
#define PCNET_RX_BUFF_COUNT 32 // Linux & Shrine Driver use 32 and 8 for these, we could allow more if wanted.
#define PCNET_TX_BUFF_COUNT 8
#define PCNET_RX_BUFF_COUNT 32 // Linux & Shrine Driver use 32 and 8 for
#define PCNET_TX_BUFF_COUNT 8 // these, we could allow more if wanted.
#define PCNET_DESCRIPTORf_ENP 24
#define PCNET_DESCRIPTORf_STP 25
@ -81,7 +81,7 @@
class CPCNet
{
CPCIDev *pci;
U64 mac_address; //MAC address is first 6 bytes of PCNet EEPROM (page # ?)
U8 mac_address[6]; //MAC address is first 6 bytes of PCNet EEPROM (page # ? )
I64 current_rx_de_index; // Current Receive DE being processed. Gets incremented, wrapped to 0 at max of PCNET_RX_BUFF_COUNT.
I64 current_tx_de_index; // Current Transmit DE being processed. Gets incremented, wrapped to 0 at max of PCNET_TX_BUFF_COUNT.
@ -97,29 +97,36 @@ class CPCNet
} pcnet; // pcnet is the global variable we store all of this into.
class CPCNetDescriptorEntry
{/* AMD PCNet datasheet p.1-991 & p.1-994 NOTE: chart typo on 1-994, see ONES and BCNT on 1-995.
TX and RX DE's are the same size (16-Bytes) and structure, but have different registers and functions.
The RX and TX DE buffers of the CPCNet class are allocated to a certain amount of these DEs. */
{/* AMD PCNet datasheet p.1-991 & p.1-994 NOTE: chart typo on 1-994, see ONES and BCNT on 1-995.
TX and RX DE's are the same size (16-Bytes) and structure,
but have different registers and functions.
The RX and TX DE buffers of the CPCNet class
are allocated to a certain amount of these DEs. */
U32 buffer_addr;
U32 status1;
U32 status2;
U32 reserved;
};
CPCIDev *PCNetPCIDevFind()
{// Find and return PCNetII card as a CPCIDev pointer.
return PCIDevFind(,,PCNET_VENDOR_ID,PCNET_DEVICE_ID);
}
U32 PCNetGetIOBase()
{/* Return memory IO base address of PCNet card. Bits 0-4 are not for the IO base, so an AND NOT 0x1F ignores those bits. */
{/* Return memory IO base address
of PCNet card. Bits 0-4 are not
for the IO base, so an AND with
~0x1F ignores those bits. */
U32 io_base = pcnet.pci->base[0] & ~0x1F;
return io_base;
}
U0 PCNetReset()
{/* Reads the 32- and 16-bit RESET registers, which,
regardless of which mode the card is in, will reset it back to 16-bit mode. */
{/* Reads the 32- and 16-bit RESET registers,
which, regardless of which mode the card is in,
will reset it back to 16-bit mode. */
InU32(PCNetGetIOBase + PCNET_DW_RESET);
InU16(PCNetGetIOBase + PCNET_WD_RESET);
Busy(5); // OSDev says minimum 1 æS
@ -127,31 +134,49 @@ U0 PCNetReset()
U0 PCNetEnter32BitMode()
{/* AMD PCNet datasheet p. 1-930
Summary: A 32-bit write (while in 16-bit mode) to RDP will cause 16-bit mode exit and immediate entry into 32-bit mode. */
Summary: A 32-bit write (while in 16-bit mode)
to RDP will cause 16-bit mode exit
and immediate enter into 32-bit mode. */
OutU32(PCNetGetIOBase + PCNET_DW_RDP, 0);
}
U0 PCNetWriteRAP(U32 value)
{/* AMD PCNet datasheet p. 1-952
Summary: Register Address Pointer register
value will indicate which CSR / BCR register
we want to access in RDP / BDP. */
OutU32(PCNetGetIOBase + PCNET_DW_RAP, value);
}
U0 PCNetWriteCSR(U32 csr, U32 value)
{/* AMD PCNet datasheet p. 1-952
Summary: Control and Status Registers are accessed via the RDP (Register Data Port).
Which CSR is selected is based on the value in the RAP (Register Address Port). */
OutU32(PCNetGetIOBase + PCNET_DW_RAP, csr);
Summary: Control and Status Registers are
accessed via the RDP (Register Data Port).
Which CSR is selected is based on the value
in the RAP. */
PCNetWriteRAP(csr);
OutU32(PCNetGetIOBase + PCNET_DW_RDP, value);
}
U32 PCNetReadCSR(U32 csr)
{/* AMD PCNet datasheet p. 1-952
Summary: Control and Status Registers are accessed via the RDP (Register Data Port).
Which CSR is selected is based on the value in the RAP. */
OutU32(PCNetGetIOBase + PCNET_DW_RAP, csr);
Summary: Control and Status Registers are
accessed via the RDP (Register Data Port).
Which CSR is selected is based on the value
in the RAP. */
PCNetWriteRAP(csr);
return InU32(PCNetGetIOBase + PCNET_DW_RDP);
}
U0 PCNetSetSWStyle()
{/* AMD PCNet datasheet p. 1-968
In CSR58 (Software Style), the 8-bit SWSTYLE register dictates interpretation of certain bits in the CSR space,
and widths of descriptors and initialization block.
In PCNet-PCI mode, CSR4 bits function as defined in the datasheet, and TMD1[29] functions as ADD_FCS. */
In CSR58 (Software Style), the 8-bit
SWSTYLE register dictates interpretation of certain
bits in the CSR space, and widths of descriptors and
initialization block. In PCINet-PCI mode, CSR4 bits
function as defined in the datasheet , and TMD1[29]
functions as ADD_FCS. */
U32 csr = PCNetReadCSR(PCNET_CSR_SOFTWARESTYLE);
csr &= ~0xFF; // clears first 8 bits: SWSTYLE 8-bit register.
@ -163,23 +188,35 @@ U0 PCNetSetSWStyle()
U0 PCNetGetMAC()
{/* AMD PCNet datasheet p. 1-887, 1-931, 1-937
MAC address stored at first 6 bytes of PCNet EEPROM.
EEPROM addresses shadow-copied to APROM at hardware init. APROM accessible at first 16 bytes of PCI IO space. */
EEPROM addresses shadow-copied to APROM at hardware init.
APROM accessible at first 16 bytes of PCI IO space. */
I64 i;
for (i = 0; i < 6; i++)
pcnet.mac_address.u8[i] = InU8(PCNetGetIOBase + i);
{
pcnet.mac_address[i] = InU8(PCNetGetIOBase + i);
}
}
U0 PCNetInitDescriptorEntry(CPCNetDescriptorEntry *entry, U32 buffer_addr, I64 is_rx)
U0 PCNetInitDescriptorEntry(CPCNetDescriptorEntry *entry, U32 buffer_address, I64 is_rx)
{
entry->buffer_addr = buffer_addr;
entry->buffer_addr = buffer_address;
/* AMD PCNet datasheet p.1-991.
BCNT is the usable buffer length, expressed as first 12 bits of 2s-complement of desired length.
Bits 0-11 of a DE are for the buffer byte count (BCNT),and bits 12-15 of a DE must be written all ones (ONES)*/
entry->status1 |= -ETHERNET_MIN_FRAME_SIZE & 0xFFF; //Sets BCNT reg (first 12 bits) in DE TMD1/RMD1, as 2s complement of the desired length.
BCNT is the usable buffer length, expressed as first
12 bits of 2s-complement of desired length.
Bits 0-11 of a DE are for the buffer byte count (BCNT),
and bits 12-15 of a DE must be written all ones (ONES) */
U16 buffer_byte_count = -ETHERNET_FRAME_SIZE; // Sets up as 2s complement of the desired length.
buffer_byte_count &= 0x0FFF; // Masks 0 over everything except bits 0-11.
entry->status1 |= buffer_byte_count; // Sets BCNT reg (first 12 bits) in DE TMD1/RMD1.
entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1/RMD1 as all ones.
//if this is a Receive DE, give ownership to the card so the PCNet can fill them.
if (is_rx) Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
if (is_rx)
Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
ClassRep(entry);
}
U0 PCNetAllocateBuffers()
@ -187,82 +224,131 @@ U0 PCNetAllocateBuffers()
I64 de_index; // used in for loops for TX and RX DE access.
/* AMD PCNet datasheet p.1-913, p.1-990
When SSIZE32 = 1, Descriptor Ring Entry Base Address must be on 16-byte boundary. (TDRA[3:0] = 0, RDRA[3:0] = 0) */
pcnet.rx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_RX_BUFF_COUNT, 16, Fs->code_heap);
pcnet.tx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_TX_BUFF_COUNT, 16, Fs->code_heap);
When SSIZE32=1, Descriptor Ring Entry Base Address
must be on 16-byte boundary. (TDRA[3:0]=0, RDRA[3:0]=0) */
pcnet.rx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_RX_BUFF_COUNT,
16,
Fs->code_heap);
pcnet.tx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_TX_BUFF_COUNT,
16,
Fs->code_heap);
//Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000
pcnet.rx_de_buffer = dev.uncached_alias + pcnet.rx_de_buffer_phys; // we want uncached
pcnet.tx_de_buffer = dev.uncached_alias + pcnet.tx_de_buffer_phys; // access to these.
pcnet.rx_buffer_addr = CAlloc(ETHERNET_MIN_FRAME_SIZE * PCNET_RX_BUFF_COUNT, Fs->code_heap);//Shrine has a TODO to figure out
pcnet.tx_buffer_addr = CAlloc(ETHERNET_MIN_FRAME_SIZE * PCNET_TX_BUFF_COUNT, Fs->code_heap);//if these should be uncached too.
//note, p.1-991,1-994: RBADR is only 32 bits wide.
pcnet.rx_buffer_addr = CAlloc(ETHERNET_FRAME_SIZE * PCNET_RX_BUFF_COUNT, //Shrine has a TODO to figure out
Fs->code_heap);
pcnet.tx_buffer_addr = CAlloc(ETHERNET_FRAME_SIZE * PCNET_TX_BUFF_COUNT, //if these should be uncached too.
Fs->code_heap); //note, p.1-991,1-994: RBADR is only 32 bits wide.
//Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000
CPCNetDescriptorEntry *entry = pcnet.rx_de_buffer;
for (de_index = 0; de_index < PCNET_RX_BUFF_COUNT; de_index++)
{
PCNetInitDescriptorEntry(&entry[de_index], pcnet.rx_buffer_addr, TRUE); // TRUE for is_rx.
}
entry = pcnet.tx_de_buffer;
for (de_index = 0; de_index < PCNET_TX_BUFF_COUNT; de_index++)
PCNetInitDescriptorEntry(&entry[de_index], pcnet.tx_buffer_addr, FALSE); // FALSE for is_rx.
{
PCNetInitDescriptorEntry(&entry[de_index], pcnet.tx_buffer_addr, FALSE); // FALSE for is_rx.
}
}
U0 PCNetDirectInit()
{/* AMD PCNet datasheet p. 1-1021
Instead of setting up an initialization block,
direct writes to the necessary CSRs can be used to manually initialize the PCNet card. */
Instead of setting up initialization block,
direct writes to the necessary CSRs can be
used to manually initialize the PCNet card. */
/* AMD PCNet datasheet p.1-991
If Logical Address Filter is set as all 0, all incoming logical addresses are rejected. Disables multicast. */
If Logical Address Filter is set as
all 0, all incoming logical addresses
are rejected. Disables multicast. */
PCNetWriteCSR(PCNET_CSR_LADRF0, 0);
PCNetWriteCSR(PCNET_CSR_LADRF1, 0);
PCNetWriteCSR(PCNET_CSR_LADRF2, 0);
PCNetWriteCSR(PCNET_CSR_LADRF3, 0);
/* AMD PCNet datasheet p.1-960, 1-961
The Physical Address is the MAC.
The first 16 bits of CSRs 12-14 are for the Physical Address, the upper bits are reserved, written 0 read undefined.*/
/* The Physical Address is the MAC.
AMD PCNet datasheet p.1-960, 1-961
The first 16 bits of CSRs 12-14 are
for the Physical Address, the upper bits
are reserved, written 0 read undefined.
PCNetWriteCSR(PCNET_CSR_PADR0, pcnet.mac_address.u16[0]);
PCNetWriteCSR(PCNET_CSR_PADR1, pcnet.mac_address.u16[1]);
PCNetWriteCSR(PCNET_CSR_PADR2, pcnet.mac_address.u16[2]);
The OR and bit-shift of 8 allows writing
separate U8 values in the correct locations
of the CSR. */
PCNetWriteCSR(PCNET_CSR_PADR0,
pcnet.mac_address[0] | (pcnet.mac_address[1] << 8));
PCNetWriteCSR(PCNET_CSR_PADR1,
pcnet.mac_address[2] | (pcnet.mac_address[3] << 8));
PCNetWriteCSR(PCNET_CSR_PADR2,
pcnet.mac_address[4] | (pcnet.mac_address[5] << 8));
/* AMD PCNet datasheet p.1-961, 1-962, 1-963
Refer to datasheet for specifics. Most relevant, when setting Mode to 0, promiscuous mode is is disabled,
TX and RX enabled, enable RX broadcast and unicast. */
Refer to datasheet for specifics.
Most relevant, when setting Mode to 0,
promiscuous mode is is disabled, TX and
RX enabled, enable RX broadcast and unicast. */
PCNetWriteCSR(PCNET_CSR_MODE, 0);
/* AMD PCNet datasheet p.1-964
CSR 24 and 25 need to be filled with the lower and upper 16 bits, respectively, of the address of the RX packet ring. Likewise for
CSR 30 and 31 for the TX packet ring.*/
CSR 24 and 25 need to be filled
with the lower and upper 16 bits,
respectively, of the address of
the RX packet ring. Likewise for
CSR 30 and 31 for the TX packet ring.
PCNetWriteCSR(PCNET_CSR_BADRL, pcnet.rx_buffer_addr.u16[0]);
PCNetWriteCSR(PCNET_CSR_BADRU, pcnet.rx_buffer_addr.u16[1]);
0xFFFF AND on address will leave
only lower 16 bits remaining.
PCNetWriteCSR(PCNET_CSR_BADTL, pcnet.tx_buffer_addr.u16[0]);
PCNetWriteCSR(PCNET_CSR_BADTU, pcnet.tx_buffer_addr.u16[1]);
Bitshift right of 16 will replace
first 16 bits with upper 16 bits,
remaining bits cleared.*/
PCNetWriteCSR(PCNET_CSR_BADRL,
pcnet.rx_buffer_addr & 0xFFFF);
PCNetWriteCSR(PCNET_CSR_BADRU,
pcnet.rx_buffer_addr >> 16);
PCNetWriteCSR(PCNET_CSR_BADTL,
pcnet.tx_buffer_addr & 0xFFFF);
PCNetWriteCSR(PCNET_CSR_BADTU,
pcnet.tx_buffer_addr >> 16);
/* AMD PCNet datasheet p. 1-967
Default value at hardware init is all 0.
Standard init block process sets this, but if doing directly it is imperative to manually set it 0. */
Default value at hardware init is
all 0. Standard init block process
sets this, but if doing directly
it is imperative to manually set it 0. */
PCNetWriteCSR(PCNET_CSR_POLLINT, 0);
/* AMD PCNet datasheet p. 1-970
Receive and Transmit Ring Length CSRs bits 0-15 need to be set as the 2s complement of the ring length.
The AND with 0xFFFF clears the upper Reserved bits, which are to be written as zeroes read undefined. */
PCNetWriteCSR(PCNET_CSR_RXRINGLEN, -PCNET_RX_BUFF_COUNT & 0xFFFF);
PCNetWriteCSR(PCNET_CSR_TXRINGLEN, -PCNET_TX_BUFF_COUNT & 0xFFFF);
Receive and Transmit Ring Length CSRs
bits 0-15 need to be set as the 2s complement
of the ring length. The AND with 0xFFFF clears
the upper Reserved bits, which are to be written
as zeroes read undefined. */
PCNetWriteCSR(PCNET_CSR_RXRINGLEN,
-PCNET_RX_BUFF_COUNT & 0xFFFF);
PCNetWriteCSR(PCNET_CSR_TXRINGLEN,
-PCNET_TX_BUFF_COUNT & 0xFFFF);
}
U0 PCNetSetInterruptCSR()
{/* AMD PCNet datasheet p.1-952, 1-953, 1-954, 1-955, 1-956, 1-957
Refer to datasheet for specifics on the Interrupt Masks.
Most of these, when set 0, allow interrupts to be set in CSR0.
We set Big-Endian disabled, RX interrupts enabled, Init Done interrupt disabled, and TX interrupt disabled. */
We set Big-Endian disabled, RX interrupts
enabled, Init Done interrupt disabled, and TX interrupt
disabled. */
U32 csr = PCNetReadCSR(PCNET_CSR_INTERRUPTS);
Btr(&csr, PCNET_INT_BSWP);
@ -276,7 +362,9 @@ U0 PCNetSetInterruptCSR()
U0 PCNetEnableTXAutoPad()
{/* AMD PCNet datasheet p.1-958
Setting bit 11 (Auto Pad Transmit) allows short transmit frames to be automatically extended to 64 bytes. */
Setting bit 11 (Auto Pad Transmit) allows
shoft transmit frames to be automatically
extended to 64 bytes. */
U32 csr = PCNetReadCSR(PCNET_CSR_FEATURECTRL);
Bts(&csr, PCNET_FEATURE_APADXMT);
@ -287,7 +375,11 @@ U0 PCNetEnableTXAutoPad()
U0 PCNetExitConfigMode()
{/* AMD PCNet datasheet p.1-954
PCNet controller can be started after configuring by ensuring INIT and STOP are cleared and START bit is set in CSR0. */
PCNet controller can be started
after configuring by ensuring INIT
and STOP are cleared and START bit
is set, in Status and Control Register
(CSR0). */
U32 csr = PCNetReadCSR(PCNET_CSR_CTRLSTATUS);
Btr(&csr, PCNET_CTRL_INIT);
@ -295,22 +387,25 @@ U0 PCNetExitConfigMode()
Bts(&csr, PCNET_CTRL_STRT);
PCNetWriteCSR(PCNET_CSR_CTRLSTATUS, csr);
}
I64 PCNetDriverOwns(CPCNetDescriptorEntry* entry)
{/* Returns whether the value of the OWN bit of the Descriptor Entry is zero.
If 0, driver owns, if 1, PCNet card owns it. */
{/* Returns whether the value of the OWN bit of the
Descriptor Entry is zero. If 0, driver owns,
if 1, PCNet card owns it. */
return !Bt(&entry->status1, PCNET_DESCRIPTORf_OWN);
}
I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length)
{/* Transmits the packet at the current TX DE index.
The packet_buffer_out is a pointer, since we modify its value, ending with returning the index of the DE we just processed.
Length is validated to fit in BCNT.
The increment of the current TX DE index is done by assigning it the value of incrementing it AND the max DE index - 1.
This will increment it as well as wrap back to 0 if we hit the max DE index. */
{/* Transmits the packet at the current TX DE index. The packet_buffer_out
is a pointer, since we modify its value, ending with returning the
index of the DE we just processed. Length is validated to fit in BCNT.
The increment of the current TX DE index is done by assigning it the
value of incrementing it AND the max DE index-1. This will increment it
as well as wrap back to 0 if we hit the max DE index. */
U16 buffer_byte_count;
I64 de_index = pcnet.current_tx_de_index;
if (length > 0xFFF)
@ -319,6 +414,7 @@ I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length)
throw('PCNet');
}
CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index];
if (!PCNetDriverOwns(entry))
@ -328,27 +424,36 @@ I64 PCNetAllocateTransmitPacket(U8 **packet_buffer_out, I64 length)
}
Bts(&entry->status1, PCNET_DESCRIPTORf_STP);
Bts(&entry->status1, PCNET_DESCRIPTORf_ENP);
/* AMD PCNet datasheet p.1-991.
BCNT is the usable buffer length, expressed as first 12 bits of 2s-complement of desired length.
BCNT is the usable buffer length, expressed as first
12 bits of 2s-complement of desired length.
Bits 0-11 of a DE are for the buffer byte count (BCNT),
and bits 12-15 of a DE must be written all ones (ONES) */
entry->status1 |= -length & 0xFFF; // Sets BCNT reg (first 12 bits) in DE TMD1, as 2s complement of the desired length.
entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1 as all ones.
buffer_byte_count = -length; // Sets up as 2s complement of the desired length.
buffer_byte_count &= 0x0FFF; // Masks 0 over everything except bits 0-11.
pcnet.current_tx_de_index = (pcnet.current_tx_de_index + 1) & (PCNET_TX_BUFF_COUNT - 1);
entry->status1 |= buffer_byte_count; // Sets BCNT reg (first 12 bits) in DE TMD1.
entry->status1 |= 0xF000; // Sets bits 12-15 (ONES) in DE TMD1 as all ones.
*packet_buffer_out = pcnet.tx_buffer_addr + (de_index * ETHERNET_MIN_FRAME_SIZE);
pcnet.current_tx_de_index = (pcnet.current_tx_de_index + 1)
& (PCNET_TX_BUFF_COUNT - 1);
*packet_buffer_out = pcnet.tx_buffer_addr + (de_index * ETHERNET_FRAME_SIZE);
return de_index;
}
U0 PCNetFinishTransmitPacket(I64 de_index)
{/* Release ownership of the packet to the PCNet card
by setting the OWN bit to 1. */
CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index];
Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
}
U0 EthernetFrameFinish(I64 de_index)
@ -357,38 +462,48 @@ U0 EthernetFrameFinish(I64 de_index)
}
I64 PCNetReceivePacket(U8 **packet_buffer_out, U16 *packet_length_out)
{/* Receives the packet at the current RX DE index.
Parameters are both pointers, since we modify the value at the packet_buffer_out,
and at the packet_length, ending with returning the index of the DE we just processed.
The MCNT is stored at the first two bytes of the RMD2.
We AND with 0xFFFF to only take in those first two bytes: that is the packet_length.
The increment of the current RX DE index is done by assigning it the value of incrementing it AND the max DE index - 1.
This will increment it as well as wrap back to 0 if we hit the max DE index. */
ZenithLog("PCNet received packet. %X , %X",packet_buffer_out, packet_length_out);
{/* Receives the packet at the current RX DE index. Parameters
are both pointers, since we modify the value at the packet_buffer_out,
and at the packet_length, ending with returning the index of the DE
we just processed.
The MCNT is stored at the first two bytes of the RMD2. We AND with
0xFFFF to only take in those first two bytes: that is the packet_length.
The increment of the current RX DE index is done by assigning it the
value of incrementing it AND the max DE index-1. This will increment it
as well as wrap back to 0 if we hit the max DE index. */
ZenithErr("PCNet received packet. %X , %X",packet_buffer_out,packet_length_out);
I64 de_index = pcnet.current_rx_de_index;
CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index];
U16 packet_length = entry->status2 & 0xFFFF;
pcnet.current_rx_de_index = (pcnet.current_rx_de_index + 1) & (PCNET_RX_BUFF_COUNT - 1);
pcnet.current_rx_de_index = (pcnet.current_rx_de_index + 1)
& (PCNET_RX_BUFF_COUNT - 1);
*packet_buffer_out = pcnet.rx_buffer_addr + (de_index * ETHERNET_MIN_FRAME_SIZE);
*packet_buffer_out = pcnet.rx_buffer_addr + (de_index * ETHERNET_FRAME_SIZE);
*packet_length_out = packet_length;
return de_index;
}
U0 PCNetReleaseReceivePacket(I64 de_index)
{/* Release ownership of the packet to the PCNet card
by setting the OWN bit to 1. */
CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index];
Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
}
interrupt U0 PCNetIRQ()
{// todo: comments explaining process...maybe reimplement interrupt handling altogether.
U8 *packet_buffer;
U16 packet_length;
I64 de_index;
U32 csr = PCNetReadCSR(PCNET_CSR_CTRLSTATUS);
//"Interrupt Reason: %X , %b\n",csr,csr;
@ -396,12 +511,12 @@ interrupt U0 PCNetIRQ()
while (PCNetDriverOwns(&entry[pcnet.current_rx_de_index]))
{
"%X", pcnet.current_rx_de_index;
"%X",pcnet.current_rx_de_index;
de_index = PCNetReceivePacket(&packet_buffer, &packet_length);
if (de_index >= 0) // necessary? check increment logic in PCNetReceivePacket.
{
ZenithLog("Pushing copy into Net Queue, Releasing Receive Packet");
ZenithErr("Pushing copy into Net Queue, Releasing Receive Packet");
NetQueuePushCopy(packet_buffer, packet_length);
PCNetReleaseReceivePacket(de_index);
}
@ -415,26 +530,26 @@ interrupt U0 PCNetIRQ()
}
U0 PCIRerouteInterrupts(I64 base)
{// todo: comments explaining process, maybe better var names
{ // todo: comments explaining process, maybe better var names
I64 i;
U8 *da = dev.uncached_alias + IOAPIC_REG;
U32 *_d = dev.uncached_alias + IOAPIC_DATA;
for (i = 0; i < 4; i++)
{
*da = IOREDTAB + i * 2 + 1;
*da = IOREDTAB + i*2 + 1;
*_d = dev.mp_apic_ids[INT_DEST_CPU] << 24;
*da = IOREDTAB + i * 2;
*da = IOREDTAB + i*2;
*_d = 0x4000 + base + i;
}
}
U0 PCNetSetupInterrupts()
{ // todo: comments explaining process
IntEntrySet(I_USER + 0, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER + 1, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER + 2, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER + 3, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER+0, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER+1, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER+2, &PCNetIRQ, IDTET_IRQ);
IntEntrySet(I_USER+3, &PCNetIRQ, IDTET_IRQ);
PCIRerouteInterrupts(I_USER);
}
@ -446,33 +561,58 @@ U0 PCNetInit()
if (!pcnet.pci)
return; // if we don't find the card, quit.
//Clear command register of PCNet PCI device, set IO Enable and Bus Master Enable bits of the register.
PCIWriteU16(pcnet.pci->bus, pcnet.pci->dev, pcnet.pci->fun, PCI_REG_COMMAND, PCNET_CMDF_IOEN | PCNET_CMDF_BMEN);
/* Clear command register of PCNet
PCI device, set IO Enable and Bus
Master Enable bits of the register. */
PCIWriteU16(pcnet.pci->bus,
pcnet.pci->dev,
pcnet.pci->fun,
PCI_REG_COMMAND,
PCNET_CMDF_IOEN | PCNET_CMDF_BMEN);
PCNetReset;
PCNetEnter32BitMode;
PCNetSetSWStyle;
PCNetGetMAC;
// OSDev has code ensuring auto selected connection...
PCNetAllocateBuffers;
PCNetDirectInit;
PCNetSetInterruptCSR;
PCNetEnableTXAutoPad;
PCNetExitConfigMode;
PCNetSetupInterrupts;
Sleep(100);//? necessary?
ClassRep(&pcnet);
"pcnet->rx_de_buffer: %X\n",pcnet.rx_de_buffer;
"pcnet->tx_de_buffer: %X\n",pcnet.tx_de_buffer;
"pcnet->rx_de_buffer_phys: %X\n",pcnet.rx_de_buffer_phys;
"pcnet->rx_de_buffer_phys: %X\n",pcnet.tx_de_buffer_phys;
}
I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destination_address, U16 ethertype, I64 packet_length)
{/* Allocate an Ethernet Frame for transmit.
The source and destination addresses are copied to the Frame, as well as the ethertype.
The packet_buffer_out parameter has the value at its pointer set to the payload of the Ethernet Frame. */
I64 EthernetFrameAllocate( U8 **packet_buffer_out,
U8 *source_address,
U8 *destination_address,
U16 ethertype,
I64 packet_length)
{/* Allocate an Ethernet Frame for transmit. The source
and destination addresses are copied to the Frame,
as well as the ethertype. The packet_buffer_out
parameter has the value at its pointer set to the
payload of the Ethernet Frame. */
//todo: un magic number the rest of this
U8 *ethernet_frame;
@ -482,7 +622,7 @@ I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destin
if (packet_length < ETHERNET_MIN_FRAME_SIZE)
packet_length = ETHERNET_MIN_FRAME_SIZE;
de_index = PCNetAllocateTransmitPacket(&ethernet_frame, 14 + packet_length);
de_index = PCNetAllocateTransmitPacket(&ethernet_frame, ETHERNET_MAC_HEADER_LENGTH + packet_length);
if (de_index < 0)
{
@ -493,16 +633,35 @@ I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destin
MemCopy(ethernet_frame, destination_address, MAC_ADDRESS_LENGTH);
MemCopy(ethernet_frame + MAC_ADDRESS_LENGTH, source_address, MAC_ADDRESS_LENGTH);
*&ethernet_frame[ETHERNET_ETHERTYPE_OFFSET](U16 *) = ethertype;
ethernet_frame[ETHERNET_ETHERTYPE_OFFSET] = ethertype << 8;
ethernet_frame[ETHERNET_ETHERTYPE_OFFSET + 1] = ethertype & 0xFF;
*packet_buffer_out = ethernet_frame + ETHERNET_MAC_HEADER_LENGTH;
return de_index;
}
U64 EthernetGetMAC()
U8 *EthernetGetMAC()
{
return pcnet.mac_address;
}
PCNetInit;

488
src/Home/Net/Sockets.CC Executable file
View File

@ -0,0 +1,488 @@
/* docs.idris-lang.org/en/latest/st/examples.html
beej.us/guide/bgnet/html/
Sockets are non-standard, a simple
Finite State Machine. The functions'
only args are the socket. Socket functions
requiring more parameters should be
defined at the protocol level.
The state machine exists to allow
protocol code to execute code in
the appropriate order. When calling
a socket function, code can use
the modified/unmodified states to
determine next procedure.
I included some code for IPV6, currently
unused. */
#define SOCKET_STATE_READY 0
#define SOCKET_STATE_BIND_REQ 1
#define SOCKET_STATE_CONNECT_REQ 2
#define SOCKET_STATE_BOUND 3
#define SOCKET_STATE_LISTEN_REQ 4
#define SOCKET_STATE_LISTENING 5
#define SOCKET_STATE_OPEN 6
#define SOCKET_STATE_CLOSE_REQ 7
#define SOCKET_STATE_CLOSED 8
#define SOCKET_STREAM 1
#define SOCKET_DATAGRAM 2
#define SOCKET_RAW 3
#define AF_INET 2
#define AF_INET6 10
#define INET_ADDRSTRLEN 16 //pubs.opengroup.com netinit/in.h
#define INET6_ADDRSTRLEN 46
#define INET_MIN_ADDRSTRLEN 7 // ex: len of 0.0.0.0
#define INET6_MIN_ADDRSTRLEN 2 // ie: len of ::
#define IP_PARSE_STATE_NUM 0
#define IP_PARSE_STATE_DOT 1
class CSocketAddress
{
U16 family; // 'address family, AF_xxx'
U8 data[14]; // '14 bytes of protocol address'
};
class CIPV4Address
{
U32 address; // 'in Network Byte order' ... Big Endian
};
class CIPV6Address
{
U8 address[16]; //a clear #define would be nice
};
class CIPAddressStorage
{// class specifically meant to be generic casted either IPV4 or IPV6 Address.
U8 padding[16];
};
class CSocketAddressIPV4
{
I16 family; // 'AF_INET'
U16 port; // 'in Network Byte order' ... Big Endian
CIPV4Address address;
U8 zeroes[8]; //'same size as socket address'
};
class CSocketAddressIPV6
{
U16 family; // 'AF_INET6'
U16 port; // 'in Network Byte order'... Big Endian
U32 flow_info;
CIPV6Address address;
U32 scope_id;
};
class CSocketAddressStorage
{/* 'designed to be large enough to
hold both IPV4 and IPV6 structures.' */
U16 family;
U8 padding[26];
};
class CAddressInfo
{
I32 flags;
I32 family;
I32 socket_type;
I32 protocol;
I64 address_length;
CSocketAddress *address;
U8 *canonical_name;
CAddressInfo *next;
};
class CSocket
{
U8 state;
U16 type;
U16 domain;
};
Bool IPV4AddressParse(U8 *string, U32* destination)
{
// U8* lexable_string;
// lexable_string = StrReplace(string, ",", ","); // swap any commas with an unexpected value
U8 *lexable_string = StrReplace(string, ".", ","); // swap dots with commas since Lex is easier with them.
CCompCtrl* cc = CompCtrlNew(lexable_string);
//Bts(&cc->opts, OPTf_DECIMAL_ONLY);
cc->opts |= 1 << OPTf_DECIMAL_ONLY;
I64 tk;
I64 state = IP_PARSE_STATE_NUM;
U32 temp_destination = 0;
I64 current_section = 0; // IPV4 address has 4 total sections
while (tk = Lex(cc))
{
switch (state)
{
case IP_PARSE_STATE_NUM:
switch (tk)
{
case TK_I64:
if (cc->cur_i64 > 255 || cc->cur_i64 < 0)
{
ZenithErr("Invalid value, must be 0 - 255.\n");
return FALSE;
}
if (current_section > 3)
{
ZenithErr("IP Address can only have 4 sections.\n");
return FALSE;
}
temp_destination |= cc->cur_i64 << (current_section * 8);
current_section++;
state = IP_PARSE_STATE_DOT;
break;
default:
ZenithErr("Expected decimal. \n");
return FALSE;
}
break;
case IP_PARSE_STATE_DOT:
switch (tk)
{
case ',':
state = IP_PARSE_STATE_NUM;
break;
default:
ZenithErr("Expected dot. \n");
return FALSE;
}
break;
}
}
temp_destination = EndianU32(temp_destination); // store the address in Network Byte Order (Big-Endian)
*destination = temp_destination;
"\n\n%X\n\n",temp_destination;
return TRUE;
}
I64 PresentationToNetwork(I64 address_family, U8 *string, CIPAddressStorage *destination)
{/* Converts IP string to internet address class, our inet_pton().
Destination written as CIPV4Address or CIPV6Address depending
on value of address_family.
The destination address is the generic class, functions
calling this method must cast their classes in the params.
TODO: test it more... clarify above comment? is casting shit needed if we do it like this?..
if we declare the possible address classes, then just set them to where the
destination is from param, wouldn't that suffice fully? I noticed that it wrote fine to
a pointer to CIPV4Address without any complaints that it wasn't CIPAddressStorage .. */
//CCompCtrl *cc = CompCtrlNew(string);
CIPV4Address *ipv4_address;
CIPV6Address *ipv6_address;
I64 string_length = StrLen(string);
switch (address_family)
{
case AF_INET:
if (string_length > INET_ADDRSTRLEN || string_length < INET_MIN_ADDRSTRLEN)
{
ZenithErr("IP to Socket Address failed: Invalid Input String Size.\n");
return -1;
}
ipv4_address = destination;
IPV4AddressParse(string, &ipv4_address->address);
break;
case AF_INET6:
if (string_length > INET6_ADDRSTRLEN || string_length < INET6_MIN_ADDRSTRLEN)
{
ZenithErr("IP to Socket Address failed: Invalid Input String Size.\n");
return -1;
}
ipv6_address = destination;
Debug("IPV6 support not implemented yet.\n");
break;
default:
ZenithErr("IP to Socket Address failed: Invalid Address Family.\n");
return -1;
}
//CompCtrlDel(cc);
return 0;
}
U8 *NetworkToPresentation(I64 address_family, CIPAddressStorage *source)
{ // converts socket address to IP string, our inet_ntop. Taking Shrine approach of function returns U8* .
// I64 i;
U8* ip_string;
// U8* ip_string[INET_ADDRSTRLEN];
CIPV4Address *ipv4_source;
CIPV4Address *ipv6_source;
switch (address_family)
{
case AF_INET:
ipv4_source = source;
StrPrint(ip_string, "%d.%d.%d.%d",
ipv4_source->address.u8[3],
ipv4_source->address.u8[2],
ipv4_source->address.u8[1],
ipv4_source->address.u8[0]);
break;
case AF_INET6:
ipv6_source = source;
Debug("IPV6 support not implemented yet.\n");
break;
default:
ZenithErr("Socket Address to IP failed: Invalid Address Family.\n");
break;
}
return ip_string;
}
CSocket *Socket(U16 domain, U16 type)
{
CSocket *socket = CAlloc(sizeof(CSocket));
socket->domain = domain;
socket->type = type;
socket->state = SOCKET_STATE_READY;
return socket;
}
U0 SocketStateErr(U8 *request, U8 state)
{
U8 *state_string;
switch (state)
{
case SOCKET_STATE_READY:
state_string = StrNew("READY");
break;
case SOCKET_STATE_BIND_REQ:
state_string = StrNew("BIND REQUEST");
break;
case SOCKET_STATE_CONNECT_REQ:
state_string = StrNew("CONNECT REQUEST");
break;
case SOCKET_STATE_BOUND:
state_string = StrNew("BOUND");
break;
case SOCKET_STATE_LISTEN_REQ:
state_string = StrNew("LISTEN REQUEST");
break;
case SOCKET_STATE_LISTENING:
state_string = StrNew("LISTENING");
break;
case SOCKET_STATE_OPEN:
state_string = StrNew("OPEN");
break;
case SOCKET_STATE_CLOSE_REQ:
state_string = StrNew("CLOSE REQUEST");
break;
case SOCKET_STATE_CLOSED:
state_string = StrNew("CLOSED");
break;
}
ZenithErr("Socket attempted %s while in %s state.\n", request, state_string);
}
U0 SocketAccept(CSocket *socket)
{
switch (socket->state)
{
case SOCKET_STATE_LISTENING:
/* Socket expected to stay listening.
At protocol level, a new socket 'connected'
to this one is expected to be made. */
return;
default:
SocketStateErr("ACCEPT", socket->state);
break;
}
}
U0 SocketClose(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_LISTENING:
case SOCKET_STATE_OPEN:
/* Sockets can only be closed if
they were opened or listening
to incoming connections. */
socket->state = SOCKET_STATE_CLOSE_REQ;
break;
default:
SocketStateErr("CLOSE", socket->state);
break;
}
}
U0 SocketBind(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_READY:
/* Sockets can only be bound
if they are in initial state. */
socket->state = SOCKET_STATE_BIND_REQ;
break;
default:
SocketStateErr("BIND", socket->state);
break;
}
}
U0 SocketConnect(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_READY:
/* Sockets can only be connected
if they are in initial state. */
socket->state = SOCKET_STATE_CONNECT_REQ;
break;
default:
SocketStateErr("CONNECT", socket->state);
break;
}
}
U0 SocketListen(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_BOUND:
/* A socket must be bound to
set it to listening. */
socket->state = SOCKET_STATE_LISTEN_REQ;
break;
default:
SocketStateErr("LISTEN", socket->state);
break;
}
}
U0 SocketReceive(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_OPEN:
/* Sockets can only send/recv when
they have been connected to. */
break;
default:
SocketStateErr("RECEIVE", socket->state);
break;
}
}
U0 SocketReceiveFrom(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_OPEN:
/* Sockets can only send/recv when
they have been connected to. */
break;
default:
SocketStateErr("RECEIVE FROM", socket->state);
break;
}
}
U0 SocketSend(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_OPEN:
/* Sockets can only send/recv when
they have been connected to. */
break;
default:
SocketStateErr("SEND", socket->state);
break;
}
}
U0 SocketSendTo(CSocket* socket)
{
switch (socket->state)
{
case SOCKET_STATE_OPEN:
/* Sockets can only send/recv when
they have been connected to. */
break;
default:
SocketStateErr("SEND TO", socket->state);
break;
}
}

94
src/Home/Net/Tests/Test.CC Executable file
View File

@ -0,0 +1,94 @@
//#define IP_PARSE_STATE_FIRST_NUM 0
#define IP_PARSE_STATE_NUM 1
#define IP_PARSE_STATE_DOT 2
#define IP_PARSE_STATE_ERROR 3
I64 IPV4AddressParse(U8 *string, U32 destination)
{//destination output in network order, only write on success
I64 i;
I64 parse_state = IP_PARSE_STATE_NUM;
U32 temp_destination = 0;
I64 current_chunk_index = 0;
U8 *digit_buffer = StrNew("");
// chunk is just IPV4 num 0 through 3. on last chunk, don't go to DOT state.
for (i = 0; i < 16; i++) //use the #define in Sockets
{
switch (parse_state)
{
case IP_PARSE_STATE_NUM:
switch (string[i])
{
case '.':
parse_state = IP_PARSE_STATE_DOT;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
StrPrint(digit_buffer, "%s%c", digit_buffer, string[i]);
break;
default:
ZenithErr("IPV4 Parse Failure: Unexpected char.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
break;
case IP_PARSE_STATE_DOT:
"%s, %X\n", digit_buffer, Str2I64(digit_buffer);
if (Str2I64(digit_buffer) > 255)
{
ZenithErr("IPV4 Parse Failure: Chunk exceeds 0 - 255 range.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
temp_destination |= Str2I64(digit_buffer) << (current_chunk_index * 8);
StrCopy(digit_buffer, ""); // clear digit buffer
current_chunk_index++;
if (current_chunk_index > 3)
{
ZenithErr("IPV4 Parse Failure: Too many dots in address string.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
parse_state = IP_PARSE_STATE_NUM;
break;
case IP_PARSE_STATE_ERROR:
ZenithErr("IPV4 Parse Failure: Invalid Address String.\n");
return -1; // error state!
}
}
return 0;
}

220
src/Home/Net/Tests/Test2.CC Executable file
View File

@ -0,0 +1,220 @@
//#define IP_PARSE_STATE_FIRST_NUM 0
#define IP_PARSE_STATE_NUM 1
#define IP_PARSE_STATE_DOT 2
#define IP_PARSE_STATE_ERROR 3
/*
U8 *StrDotReplace(U8* source_string)
{
I64 i;
U8 *result = StrNew(source_string);
for (i = 0; i < StrLen(result); i++)
{
if (result[i] == ',')
{ // we're using commas internally for Lexing.
result[i] = '.';
} // so if we see a comma, set to dot, break lexer.
else if (result[i] == '.')
{ // Lex eats '.' as F64. Comma will split Lex tokens
result[i] = ',';
}
}
return result;
}
*/
Bool IPV4AddressParse(U8 *string, U32* destination)
{
// U8* lexable_string;
// lexable_string = StrReplace(string, ",", ","); // swap any commas with an unexpected value
U8 *lexable_string = StrReplace(string, ".", ","); // swap dots with commas since Lex is easier with them.
CCompCtrl* cc = CompCtrlNew(lexable_string);
//Bts(&cc->opts, OPTf_DECIMAL_ONLY);
cc->opts |= 1 << OPTf_DECIMAL_ONLY;
I64 tk;
I64 state = IP_PARSE_STATE_NUM;
U32 temp_destination = 0;
I64 current_section = 0; // IPV4 address has 4 total sections
while (tk = Lex(cc))
{
switch (state)
{
case IP_PARSE_STATE_NUM:
switch (tk)
{
case TK_I64:
if (cc->cur_i64 > 255 || cc->cur_i64 < 0)
{
ZenithErr("Invalid value, must be 0 - 255.\n");
return FALSE;
}
if (current_section > 3)
{
ZenithErr("IP Address can only have 4 sections.\n");
return FALSE;
}
temp_destination |= cc->cur_i64 << (current_section * 8);
current_section++;
state = IP_PARSE_STATE_DOT;
break;
default:
ZenithErr("Expected decimal. \n");
return FALSE;
}
break;
case IP_PARSE_STATE_DOT:
switch (tk)
{
case ',':
state = IP_PARSE_STATE_NUM;
break;
default:
ZenithErr("Expected dot. \n");
return FALSE;
}
break;
}
}
temp_destination = EndianU32(temp_destination); // store the address in Network Byte Order (Big-Endian)
*destination = temp_destination;
"\n\n%X\n\n",temp_destination;
return TRUE;
}
I64 IPV4AddressParseOOOOOOOOOOPS(U8 *string, U32 destination)
{//destination output in network order, only write on success
I64 i;
I64 parse_state = IP_PARSE_STATE_NUM;
U32 temp_destination = 0;
I64 current_chunk_index = 0;
U8 *digit_buffer = StrNew("");
destination = 'nigggg';
// chunk is just IPV4 num 0 through 3. on last chunk, don't go to DOT state.
for (i = 0; i < 16; i++) //use the #define in Sockets
{
switch (parse_state)
{
case IP_PARSE_STATE_NUM:
switch (string[i])
{
case '.':
parse_state = IP_PARSE_STATE_DOT;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
StrPrint(digit_buffer, "%s%c", digit_buffer, string[i]);
break;
default:
ZenithErr("IPV4 Parse Failure: Unexpected char.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
break;
case IP_PARSE_STATE_DOT:
"%s, %X\n", digit_buffer, Str2I64(digit_buffer);
if (Str2I64(digit_buffer) > 255)
{
ZenithErr("IPV4 Parse Failure: Chunk exceeds 0 - 255 range.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
temp_destination |= Str2I64(digit_buffer) << (current_chunk_index * 8);
StrCopy(digit_buffer, ""); // clear digit buffer
current_chunk_index++;
if (current_chunk_index > 3)
{
ZenithErr("IPV4 Parse Failure: Too many dots in address string.\n");
parse_state = IP_PARSE_STATE_ERROR;
break;
}
parse_state = IP_PARSE_STATE_NUM;
break;
case IP_PARSE_STATE_ERROR:
ZenithErr("IPV4 Parse Failure: Invalid Address String.\n");
return -1; // error state!
}
}
return 0;
}

BIN
src/Home/Net/Tests/Test3.CC Executable file

Binary file not shown.

BIN
src/Home/Net/Tests/Test4.CC Executable file

Binary file not shown.

BIN
src/Home/Net/Tests/Test5.CC Executable file

Binary file not shown.

90
src/Home/Net/Tests/Test6.CC Executable file
View File

@ -0,0 +1,90 @@
/*
CUDPSocket *s = UDPSocket(AF_INET);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
ClassRep(addr);
Class:"CSocketAddressIPV4"
15A984E30 $FG,2$family :$FG$0000
15A984E32 $FG,2$port :$FG$0000
15A984E34 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A984E34 $FG,2$address :$FG$00000000
$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000360s
addr->family = AF_INET;
0.000006s ans=0x00000002=2
addr->port = EndianU16(0xDEAF);
0.000007s ans=0x0000AFDE=45022
PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address);
66210601
0.000027s ans=0x00000000=0
ClassRep(addr);
Class:"CSocketAddressIPV4"
15A984E30 $FG,2$family :$FG$0002
15A984E32 $FG,2$port :$FG$AFDE
15A984E34 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A984E34 $FG,2$address :$FG$66210601
$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000385s
UDPSocketBind(s, addr);
0.000008s ans=0x00000000=0
ClassRep(s);
Class:"CUDPSocket"
15A982C58 $TR,"socket"$
$ID,2$Class:"CSocket"
15A9931E8 $FG,2$state :$FG$03
15A9931E9 $FG,2$type :$FG$0002
15A9931EB $FG,2$domain :$FG$0002
$ID,-2$15A982C60 $FG,2$receive_timeout_ms :$FG$0000000000000000
15A982C68 $FG,2$receive_max_timeout :$FG$0000000000000000
15A982C70 $FG,2$receive_buffer :$FG$
15A982C78 $FG,2$receive_len :$FG$0000000000000000
15A982C80 $TR-C,"receive_address"$
$ID,2$Class:"CSocketAddressIPV4"
15A982C80 $FG,2$family :$FG$0002
15A982C82 $FG,2$port :$FG$0000
15A982C84 $TR,"address"$
$ID,2$$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
$ID,-2$15A982C90 $FG,2$bound_to :$FG$DEAF
0.000526s
ClassRep(&s->receive_address);
Class:"CSocketAddressIPV4"
15A982C80 $FG,2$family :$FG$0002
15A982C82 $FG,2$port :$FG$0000
15A982C84 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A982C84 $FG,2$address :$FG$66210601
$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000323s
*/
CUDPSocket *s = UDPSocket(AF_INET);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
ClassRep(s);
ClassRep(addr);
"\nSetting addr family to AF_INET and port to 0xDEAF in B.E., then P to N with 102.33.6.1\n";
addr->family = AF_INET;
addr->port = EndianU16(0xDEAF);
PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address);
ClassRep(addr);
"\nUDPSocket bind with socket to addr\n";
UDPSocketBind(s, addr);
ClassRep(s);
ClassRep(&s->receive_address);
UDPSocketClose(s);

129
src/Home/Net/Tests/Test7.CC Executable file
View File

@ -0,0 +1,129 @@
/*
CUDPSocket *s = UDPSocket(AF_INET);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
ClassRep(addr);
Class:"CSocketAddressIPV4"
15A984E30 $FG,2$family :$FG$0000
15A984E32 $FG,2$port :$FG$0000
15A984E34 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A984E34 $FG,2$address :$FG$00000000
$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000360s
addr->family = AF_INET;
0.000006s ans=0x00000002=2
addr->port = EndianU16(0xDEAF);
0.000007s ans=0x0000AFDE=45022
PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address);
66210601
0.000027s ans=0x00000000=0
ClassRep(addr);
Class:"CSocketAddressIPV4"
15A984E30 $FG,2$family :$FG$0002
15A984E32 $FG,2$port :$FG$AFDE
15A984E34 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A984E34 $FG,2$address :$FG$66210601
$ID,-2$15A984E38 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000385s
UDPSocketBind(s, addr);
0.000008s ans=0x00000000=0
ClassRep(s);
Class:"CUDPSocket"
15A982C58 $TR,"socket"$
$ID,2$Class:"CSocket"
15A9931E8 $FG,2$state :$FG$03
15A9931E9 $FG,2$type :$FG$0002
15A9931EB $FG,2$domain :$FG$0002
$ID,-2$15A982C60 $FG,2$receive_timeout_ms :$FG$0000000000000000
15A982C68 $FG,2$receive_max_timeout :$FG$0000000000000000
15A982C70 $FG,2$receive_buffer :$FG$
15A982C78 $FG,2$receive_len :$FG$0000000000000000
15A982C80 $TR-C,"receive_address"$
$ID,2$Class:"CSocketAddressIPV4"
15A982C80 $FG,2$family :$FG$0002
15A982C82 $FG,2$port :$FG$0000
15A982C84 $TR,"address"$
$ID,2$$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
$ID,-2$15A982C90 $FG,2$bound_to :$FG$DEAF
0.000526s
ClassRep(&s->receive_address);
Class:"CSocketAddressIPV4"
15A982C80 $FG,2$family :$FG$0002
15A982C82 $FG,2$port :$FG$0000
15A982C84 $TR-C,"address"$
$ID,2$Class:"CIPV4Address"
15A982C84 $FG,2$address :$FG$66210601
$ID,-2$15A982C88 $FG,2$zeroes :$FG$00 00 00 00 00 00 00 00
0.000323s
*/
"\nMaking new socket\n";
CUDPSocket *s = UDPSocket(AF_INET);
CSocketAddressIPV4 *addr = CAlloc(sizeof(CSocketAddressIPV4));
ClassRep(s);
ClassRep(addr);
"\nSetting addr family to AF_INET and port to 0xDEAF in B.E., then P to N with 102.33.6.1\n";
addr->family = AF_INET;
addr->port = EndianU16(0xDEAF);
PresentationToNetwork(AF_INET,"102.33.6.1",&addr->address);
ClassRep(addr);
"\nUDPSocket bind with socket to addr\n";
UDPSocketBind(s, addr);
ClassRep(s);
ClassRep(&s->receive_address);
"\nGlobal Tree:\n";
ClassRep(udp_globals.bound_socket_tree);
"\nGlobal Tree: Port 0xDEAF Queue\n";
ClassRep(udp_globals.bound_socket_tree->queue);
///
"\nMaking new socket\n";
CUDPSocket *s2 = UDPSocket(AF_INET);
CSocketAddressIPV4 *addr2 = CAlloc(sizeof(CSocketAddressIPV4));
ClassRep(s2);
ClassRep(addr2);
"\nSetting addr2 family to AF_INET and port to 0xDEAF in B.E., then P to N with 104.32.3.66\n";
addr2->family = AF_INET;
addr2->port = EndianU16(0xDEAF);
PresentationToNetwork(AF_INET,"104.32.3.66",&addr2->address);
ClassRep(addr2);
"\nUDPSocket bind with socket2 to addr2\n";
UDPSocketBind(s2, addr2);
ClassRep(s2);
ClassRep(&s2->receive_address);
"\nGlobal Tree:\n";
ClassRep(udp_globals.bound_socket_tree);
"\nGlobal Tree: Port 0xDEAF Queue\n";
ClassRep(udp_globals.bound_socket_tree->queue);
"\nClosing first socket\n";
UDPSocketClose(s);
"\nGlobal Tree:\n";
ClassRep(udp_globals.bound_socket_tree);
"\nGlobal Tree: Port 0xDEAF Queue\n";
ClassRep(udp_globals.bound_socket_tree->queue);

614
src/Home/Net/UDP.CC Executable file
View File

@ -0,0 +1,614 @@
//#include "IPV4"
#include "ICMP" // this is wrong and only doing this because we're just in dev right now. probably need approach like Shrine, MakeNet, idk.
#include "Sockets"
#define UDP_MAX_PORT 65535
class CUDPHeader
{
U16 source_port;
U16 destination_port;
U16 length;
U16 checksum;
};
class CUDPSocket
{
CSocket* socket;
I64 receive_timeout_ms;
I64 receive_max_timeout;
U8 *receive_buffer;
I64 receive_len;
CSocketAddressIPV4 receive_address; // should this change to Storage class for IPV6 later ?
U16 bound_to; // represents the currently bound port
};
////////////////////////////////////////////////////
// UDP Bound Socket Tree Classes & Functions
class CUDPTreeQueue
{ // next, last for CQueue implementation.
CUDPTreeQueue *next;
CUDPTreeQueue *last;
CUDPSocket* socket;
};
class CUDPTreeNode
{
I64 port;
CUDPTreeNode *left;
CUDPTreeNode *right;
CUDPTreeQueue* queue;
};
CUDPTreeNode *UDPTreeNodeInit()
{ // init new empty tree/node.
CUDPTreeNode *tree_node = CAlloc(sizeof(CUDPTreeNode));
return tree_node;
}
U0 UDPTreeNodeAdd(CUDPTreeNode *node, CUDPTreeNode *tree)
{ // using temp and last allows avoiding recursion and non-growing stack issues.
CUDPTreeNode *temp_tree = tree;
CUDPTreeNode *last_tree = temp_tree;
while (temp_tree)
{
if (node->port < temp_tree->port)
{ // if node smaller, go left
last_tree = temp_tree;
temp_tree = temp_tree->left;
}
else
{ // if node equal or larger, go right
last_tree = temp_tree;
temp_tree = temp_tree->right;
} // at the end of this, this _should_ result in last_tree
} // being the resulting tree to store the node inside of. i guess recompute the direction and set.
if (node->port < last_tree->port)// if node smaller, go left
last_tree->left = node;
else // if node equal or larger, go right
last_tree->right = node;
}
CUDPTreeNode *UDPTreeNodeParamAdd(I64 node_port, CUDPTreeNode *tree)
{ // add a node using params, return pointer to the node
CUDPTreeNode *result = UDPTreeNodeInit;
result->port = node_port;
UDPTreeNodeAdd(result, tree);
return result;
}
CUDPTreeNode *UDPTreeNodeParamInit(I64 port)
{
CUDPTreeNode *result = UDPTreeNodeInit;
result->port = port;
return result;
}
CUDPTreeNode *UDPTreeNodeFind(I64 port, CUDPTreeNode *tree)
{
CUDPTreeNode *temp_tree = tree;
while (temp_tree)
{
if (port < temp_tree->port) // if value smaller, go left
temp_tree = temp_tree->left;
else if (port > temp_tree->port) // if value larger, go right
temp_tree = temp_tree->right;
else // if value equal, match! i guess?
break;
}
return temp_tree; // ! NULL if not found.
}
CUDPTreeNode *UDPTreeNodePop(I64 port, CUDPTreeNode *tree)
{ // mimics TreeNodeFind. pops whole sub-tree, original tree loses whole branch.
CUDPTreeNode *parent_tree = tree;
CUDPTreeNode *temp_tree = parent_tree;
Bool is_left = FALSE;
Bool is_right = FALSE;
while (temp_tree)
{
if (port < temp_tree->port)
{
parent_tree = temp_tree;
temp_tree = temp_tree->left;
is_right = FALSE;
is_left = TRUE;
}
else if (port > temp_tree->port)
{
parent_tree = temp_tree;
temp_tree = temp_tree->right;
is_right = TRUE;
is_left = FALSE;
}
else
break;
}
if (temp_tree)
{ //if we found it, clear its parents link to the node
if (is_left)
{
parent_tree->left = NULL;
}
else if (is_right)
{
parent_tree->right = NULL;
}
}
return temp_tree; // NULL if not found.
}
CUDPTreeNode *UDPTreeNodeSinglePop(I64 port, CUDPTreeNode *tree)
{ // pop whole sub-tree, then add back in its sub-trees. TODO: should we leave the pointers in the node or clear them ?
CUDPTreeNode *node = UDPTreeNodePop(port, tree);
CUDPTreeNode *left = node->left;
CUDPTreeNode *right = node->right;
if (node)
{
if (left)
{
UDPTreeNodeAdd(left, tree);
}
if (right)
{
UDPTreeNodeAdd(right, tree);
}
}
// ... see the TODO ^
// node->left = NULL;
// node->right = NULL;
return node;
}
U0 UDPTreeNodeFree(CUDPTreeNode *node)
{ // only clears and frees the node. !! if node has subtrees, they will be left floating. use with caution to avoid memory leaks
// ... uh.. what to do with the inner CTreeQueue floating around ..? we need to fix that too right?
// .. what does CQueue functions give us. QueueRemove is our best bet, i guess it will just try to swap around the next last ptrs.
}
U0 UDPTreeNodeQueueInit(CUDPTreeNode *node)
{
node->queue = CAlloc(sizeof(CUDPTreeQueue));
QueueInit(node->queue);
}
U0 UDPTreeNodeQueueAdd(CUDPSocket* socket, CUDPTreeNode *node)
{
CUDPTreeQueue *new_entry;
if (!node->queue)
{
UDPTreeNodeQueueInit(node);
node->queue->socket = socket;
}
else
{
new_entry = CAlloc(sizeof(CUDPTreeQueue));
QueueInit(new_entry);
new_entry->socket = socket;
QueueInsert(new_entry, node->queue->last);
}
}
// refactored to UDPTreeNodeQueueSocketFind for Socket-call level functions9
CUDPTreeQueue *UDPTreeNodeQueueSocketFind(CUDPSocket* socket, CUDPTreeNode *node)
{
CUDPTreeQueue *temp_queue;
if (node->queue)
{
if (node->queue->socket == socket)
return node->queue;
temp_queue = node->queue->next;
while (temp_queue != node->queue)
{
if (temp_queue->socket == socket)
return temp_queue;
temp_queue = temp_queue->next;
}
}
return NULL;
}
CUDPTreeQueue *UDPTreeNodeQueueFind(U32 address, CUDPTreeNode *node)
{ // address should be pulled from an instance of CIPV4Address (todo.. double check what bit order we're in ?)
CUDPTreeQueue *temp_queue;
if (node->queue)
{
if (node->queue->socket->receive_address.address == address)
return node->queue;
temp_queue = node->queue->next;
while (temp_queue != node->queue)
{
if (temp_queue->socket->receive_address.address == address)
return temp_queue;
temp_queue = temp_queue->next;
}
}
return NULL;
}
CUDPTreeQueue *UDPTreeNodeQueueSocketSinglePop(CUDPSocket *socket, CUDPTreeNode *node)
{ // search by socket, pop a single UDPTreeQueue off the node, return popped queue.
CUDPTreeQueue *temp_queue = UDPTreeNodeQueueSocketFind(socket, node);
if (temp_queue)
QueueRemove(temp_queue); // links between queue entries pop out this and stitch back together. popped entry might have old links?
return temp_queue; // if not found, NULL.
}
CUDPTreeQueue *UDPTreeNodeQueueSinglePop(U32 address, CUDPTreeNode *node)
{ // pop a single UDPTreeQueue off the node, return popped queue.
CUDPTreeQueue *temp_queue = UDPTreeNodeQueueFind(address, node);
if (temp_queue)
QueueRemove(temp_queue); // links between queue entries pop out this and stitch back together. popped entry might have old links?
return temp_queue; // if not found, NULL.
}
// end UDP Bound Socket functions & classes
////////////////////////////////////////////////////
class CUDPGlobals
{
CUDPTreeNode* bound_socket_tree;
} udp_globals;
U0 UDPGlobalsInit()
{
udp_globals.bound_socket_tree = NULL;
}
I64 UDPPacketAllocate(U8** frame_out,
U32 source_ip,
U16 source_port,
U32 destination_ip,
U16 destination_port,
I64 length)
{
U8 *ethernet_frame;
I64 de_index;
CUDPHeader* header;
de_index = IPV4PacketAllocate(&ethernet_frame,
IP_PROTOCOL_UDP,
source_ip,
destination_ip,
sizeof(CUDPHeader) + length);
if (de_index < 0)
{
ZenithLog("UDP Ethernet Frame Allocate failed.\n");
return de_index;
}
header = ethernet_frame;
header->source_port = EndianU16(source_port);
header->destination_port = EndianU16(destination_port);
header->length = EndianU16(sizeof(CUDPHeader) + length);
header->checksum = 0;
*frame_out = ethernet_frame + sizeof(CUDPHeader);
}
U0 UDPPacketFinish(I64 de_index)
{ // alias for IPV4PacketFinish, alias for EthernetFrameFinish, alias for driver send packet
IPV4PacketFinish(de_index);
}
I64 UDPParsePacket(U16 *source_port_out,
U16 *destination_port_out,
U8 **data_out,
I64 *length_out,
CIPV4Packet *packet)
{
// check ip protocol? probably redundant
CUDPHeader *header = packet->data;
// Shrine has FIXME, validate packet length!
*source_port_out = EndianU16(header->source_port);
*destination_port_out = EndianU16(header->destination_port);
*data_out = packet->data + sizeof(CUDPHeader);
*length_out = packet->length - sizeof(CUDPHeader);
return 0;
}
//CUDPSocket *UDPSocket(U16 domain, U16 type) // should this even be allowed? why not just UDPSocket; ? it could just know its domain and type.
CUDPSocket *UDPSocket(U16 domain)
{
U16 type = SOCKET_DATAGRAM;
if (domain != AF_INET)
Debug("Non IPV4 UDP Sockets not implemented yet !\n");
// if (type != SOCKET_DATAGRAM)
// Debug("UDP Sockets must be of type SOCKET DATAGRAM"); // maybe just return null if wrong type and ZenithErr
CUDPSocket *udp_socket = CAlloc(sizeof(CUDPSocket));
udp_socket->socket = Socket(domain, type);
udp_socket->receive_address.family = domain; // should be INET (or INET6 i guess)
return udp_socket;
}
I64 UDPSocketBind(CUDPSocket *udp_socket, CSocketAddress *address_in) // I64 addr_len ?? does it really matter that much
{
//if we put in addr len do a check its valid for ipv4 and ipv6 based on family
CUDPTreeNode *temp_node;
CSocketAddressIPV4 *ipv4_socket_addr;
U16 port;
switch (udp_socket->socket->state)
{
case SOCKET_STATE_READY: // Socket State machine must be in init state
break;
default:
ZenithErr("Unsuccessful UDP Socket Bind: Socket state-machine must be in READY state.\n");
return -1;
}
if (udp_socket->bound_to)
{
ZenithErr("Attempted UDP Socket Bind while UDP socket currently bound.");
return -1;
}
if (address_in->family != AF_INET) Debug("Non IPV4 socket binds not implemented !");
ipv4_socket_addr = address_in;
udp_socket->receive_address.address.address = ipv4_socket_addr->address.address; // bind socket to address in parameter.
udp_socket->receive_address.port = ipv4_socket_addr->port; // ... consistency would say keep in Big Endian ...
port = EndianU16(ipv4_socket_addr->port); // port member should be Big Endian, so now we're going L.E (?)
if (udp_globals.bound_socket_tree)
{
// look for our port.
temp_node = UDPTreeNodeFind(port, udp_globals.bound_socket_tree);
if (temp_node)
{ // if we find we have bound sockets at port, check address before adding to queue
if (UDPTreeNodeQueueFind(udp_socket->receive_address.address.address, temp_node))
{
ZenithErr("Attempted UDP Socket Bind at an address already in Bound Socket Tree !\n");
return -1;
}
else
{ // if no address match, free to add socket to the node queue
UDPTreeNodeQueueAdd(udp_socket, temp_node);
}
}
else
{ // if we get no node back from port search, we didn't find it and are free to add a new node.
temp_node = UDPTreeNodeParamAdd(port, udp_globals.bound_socket_tree); // add new node with port, return its *.
UDPTreeNodeQueueAdd(udp_socket, temp_node);
}
}
else // if no bound sockets, we init the tree as a new node
{
udp_globals.bound_socket_tree = UDPTreeNodeParamInit(port); //... shouuuld be in L.E .. ?
UDPTreeNodeQueueAdd(udp_socket, udp_globals.bound_socket_tree); // add the udp socket to the port queue
// maybe more checks to do before this, dunno rn.
}
udp_socket->bound_to = port;
SocketBind(udp_socket->socket); // Advance Socket state-machine to BIND REQ state.
switch (udp_socket->socket->state)
{
case SOCKET_STATE_BIND_REQ: // if BIND request success, set BOUND.
udp_socket->socket->state = SOCKET_STATE_BOUND;
break;
default:
ZenithErr("Unsuccessful UDP Socket Bind: Misconfigured Socket state-machine.\n");
return -1;
}
return 0;
}
I64 UDPSocketClose(CUDPSocket *udp_socket)
{ // close, pop, and free the socket from the bound tree.
CUDPTreeNode *node;
CUDPTreeQueue *queue;
node = UDPTreeNodeFind(udp_socket->bound_to, udp_globals.bound_socket_tree);
if (node)
queue = UDPTreeNodeQueueSocketFind(udp_socket, node);
else
{
Debug("Didn't find node at socket during UDPSocketClose!\n");
return -1;
}
if (queue)
{
if (queue == queue->next == queue->last)
{ // queue is alone. Means port only has this address bound.
Debug("queue==next==last");
}
else
{ // queue has other addresses bound at this port. only Free the queue entry, after removing it.
// QueueRemove(queue); // QueueRemove acted strange and did not move the links?.....
// UDPTreeNodeQueueSocketSinglePop(udp_socket, node); /// ???? wtf is going on haha
// Free(udp_socket->socket);
// Free(udp_socket);// is it even possible to free a param?
// Free(queue);
}
}
else
{
Debug("Didn't find queue at socket during UDPSocketClose!\n");
return -1;
}
return 0;
}
// UDPSocketConnect (Shrine just has FIXME: 'implement')
// UDPSocketReceiveFrom
// UDPSocketSendTo
// UDPSocketSetOpt ?
// UDPHandle
// so i guess the socket functions would just act on the socket state machine.
// ZenithErr and return fail vals if socket FSM improperly used.
// if we're using a global for the bit map and socket pointer map, be careful
// with how Free is done.
UDPGlobalsInit;

Binary file not shown.

View File

@ -1,10 +0,0 @@
$TR,"Zenith"$
$ID,2$$TR,"SysMessageFlags"$
$ID,2$sys_message_flags[0]=0;
$ID,-2$$TR,"SysRegVer"$
$ID,2$registry_version=1.000;
$ID,-2$$ID,-2$$TR,"Once"$
$ID,2$$TR,"Zenith"$
$ID,2$$ID,-2$$TR,"User"$
$ID,2$$ID,-2$$ID,-2$$TR,"AutoComplete"$
$ID,2$ac.col=98;ac.row=23;$ID,-2$

View File

@ -227,9 +227,11 @@ U0 WinScrollsInit(CTask *task)
TaskDerivedValsUpdate(task);
}
#define VIEWANGLES_SPACING 22
#define VIEWANGLES_RANGE 48
//#define VIEWANGLES_RANGE 48
#define VIEWANGLES_RANGE 360
#define VIEWANGLES_BORDER 2
#define VIEWANGLES_SNAP 2
//#define VIEWANGLES_SNAP 2
#define VIEWANGLES_SNAP 1
U0 DrawViewAnglesCtrl(CDC *dc,CCtrl *c)
{

View File

@ -160,3 +160,29 @@ public U0 PaletteSetNight(Bool persistent=TRUE)
fp_set_std_palette = &PaletteSetNight;
}
//********************************************************************************
public CBGR24 gr_palette_tom[COLORS_NUM]= {
0xC9C9C9, 0x3465A4, 0x4E9A06, 0x06989A, 0xA24444, 0x75507B, 0xCE982F, 0xD3D7CF,
0x555753, 0x729FCF, 0x82BC49, 0x34E2E2, 0xAC3535, 0xAD7FA8, 0xFCE94F, 0x000000
};
public U0 PaletteSetTom(Bool persistent=TRUE)
{//Activate tom's palette.
GrPaletteSet(gr_palette_tom);
LFBFlush;
if (persistent)
fp_set_std_palette = &PaletteSetTom;
}
//********************************************************************************
public CBGR24 gr_palette_tom_light[COLORS_NUM]= {
0x000000, 0x3465A4, 0x4E9A06, 0x06989A, 0xA24444, 0x75507B, 0xCE982F, 0xD3D7CF,
0x555753, 0x729FCF, 0x82BC49, 0x34E2E2, 0xAC3535, 0xAD7FA8, 0xFCF29E, 0xC9C9C9
};
public U0 PaletteSetTomLight(Bool persistent=TRUE)
{//Activate tom's light palette.
GrPaletteSet(gr_palette_tom_light);
LFBFlush;
if (persistent)
fp_set_std_palette = &PaletteSetTomLight;
}
//********************************************************************************