IO bank objects are merely used to allocate ports, as I/O should be able to operate (when permitted) from user level. Each I/O bank contains a port allocator.
<Off IO bank. >= (U->)
//An IO bank
//
class off_IOBank : public off_HWCompResource {
private:
off_IOAllocator io_alloc; // Port allocator
<Other private members of off_IOBank. >
<Other private methods of off_IOBank. >
protected:
<Other protected methods of off_IOBank. >
public:
// Returns a pointer to the port allocator.
virtual off_IOAllocator *get_allocator(void) {return &io_alloc;}
<Other public methods of off_IOBank. >
};
Definesoff_IOBank(links are to index).
Being a subclass of CompResource, we must implement
make_available.
<Other public methods ofoff_IOBank. >= (<-U) [D->] <Implementation ofoff_CompResource::make_available. >
Such implementation uses is_local
<Other private methods of off_IOBank. >= (<-U)
// Is this identifier a local one?
boolean_t is_local(const off_io_id_t &io) {
return ( io.i_slot <= io_alloc.get_length() &&
(io_alloc+(natural_t)io.i_slot)->l_io.get_id() == io );
}
Initially IO banks are created with default values for its members.
<Other public methods ofoff_IOBank. >+= (<-U) [<-D->] // Creates an IO bank. off_IOBank(void) : off_HWCompResource(off_Protection(),OFF_PRTL_NULL, OFF_ID_NULL, OFF_MAGIC_IOBANK), <Initializeio_alloc. >, <Initialize other aggregate members ofoff_IOBank. > {;}
<Off IO bank dependencies. >= (U->) [D->] #include <klib/prot.h> // for off_Protection #include <klib/err.h> // for err_t and error numbers. #include <klib/Magic.h> // for magic numbers #include <klib/HWCompResource.h> // for off_HWCompResource #include <hw/IOAllocator.h> // for off_IOAllocator et al. #include <hw/IOPort.h> // for off_IOPort et al.
We must define the new magic number used before.
<Off magic numbers. >= [D->] OFF_MAGIC_IOBANK, // IO bank magic nb.
We can now add another case to the implementation of
Magic::nameof.
<off_Magic::nameofcase form_numbers. >= [D->] case OFF_MAGIC_IOBANK: return "off_IOBank";
IO bank users can check their references to IO banks using this method.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Does this look like a IOBank?
boolean_t valid(void) const { return get_magic() == OFF_MAGIC_IOBANK; }
The new container can also implement its get_umagic method.
<Other protected methods of off_IOBank. >= (<-U) [D->]
virtual off_magic_t get_umagic(void) { return OFF_MAGIC_IOPORT; }
After instantiation, start should be used to start IO bank
operation.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Starts normal operation for this IO bank.
err_t start( const off_mdepIOBank &mdep, const off_id_t &id );
<Off IO bank dependencies. >+= (U->) [<-D->] #include <hw/mdep/mIOBank.h> // for off_mdepIOBank
Such method receives the boot-time information about the features for the IO bank being started and sets it up accordingly. It will also set the IO bank identifier as dictated by the caller4.5.
The information contained in mdep is the first valid IO address,
the last valid IO address, the capabilities for the IO ports contained
(with bits MF_IOM if IO mapping is suported, MF_USR if users
can do I/O right from user) and the mean IO time. As of today, we
assume that IO banks always start with port number 0 and have a
pre-specified number of IO ports known by the machine dependent part.
<off_IOBank::startimplementation. >= (U->) // Starts normal operation for this IO bank. err_t off_IOBank::start( const off_mdepIOBank &mdep, const off_id_t &id ) { assert(valid()); set_id(id); r_flags |= mdep.get_flags(); <Start other members ofoff_IOBankaccording tomdep. > kcout<< "IObank #" << id << fmt(" (%d 8-bit ports) ", OFF_MDEP_NAIOP_MAX ); kcout << "started." << nl; return EOK; }
<Off IO bank dependencies. >+= (U->) [<-D->] #include <assert.h> // for assert
The flag bits are defined so that they are compatible with resource flag bits and can be checked with these methods
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Supports IO port maps.
boolean_t is_iomapable(void) const {
assert(valid()); return r_flags&OFF_IF_IOM;
}
// Supports user IO?
boolean_t is_usrio(void) const {
assert(valid()); return r_flags&OFF_IF_USR;
}
A new member, io_mdep will store the boot time information, which
is needed because it provides common information useful to IO bank
users.
<Other private members of off_IOBank. >= (<-U) [D->]
off_mdepIOBank io_mdep; // Mach. dep. information
<Start other members ofoff_IOBankaccording tomdep. >= (<-U) [D->] io_mdep=mdep;
Initially, before the IO bank is started it is initialized with silly values.
<Initialize other aggregate members of off_IOBank. >= (<-U) [D->]
io_mdep(0),
Most of the machine dependent information is exported by the IO bank.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Returns the first valid IO address.
vm_offset_t get_first(void)const{
assert(valid()); return io_mdep.get_first();
}
// Returns the last valid IO address.
vm_offset_t get_last(void)const{
assert(valid()); return io_mdep.get_last();
}
// Returns the number of IO addresses.
vm_size_t get_nports(void) const{
assert(valid()); return io_mdep.get_nports();
}
// Returns the mean IO time.
// (0 means damn slow; 255 means damn fast)
unsigned8_t get_mat(void) const {
assert(valid()); return io_mdep.get_mat();
}
The get_iosize method just returns the appropriate value found in
io_iosz. It says how many bytes an IO port has. There may be
more than one preferred IO port size.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Gets the preferred IO sizes
// above llimit bytes (or 0 if none)
vm_size_t get_iosize(const vm_size_t llimit) const;
We rely on machine dependent information to provide that information.
<off_IOBank::get_iosize implementation. >= (U->)
// Gets the preferred IO port sizes
// above llimit bytes (or 0 if none)
vm_size_t off_IOBank::get_iosize(const vm_size_t llimit) const
{
assert(valid());
for (int i=0; i<OFF_NIOSZ_MAX; i++)
if (io_mdep.get_iosz(i)>llimit)
return io_mdep.get_iosz(i);
return 0;
}
And a more efficient get_iosize routine is provided. It always returns
the minimum preferred IO size.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Returns the minimum preferred page size.
static vm_size_t get_iosize(void) { return off_mdepIOBank::get_iosz();}
<Off IO bank implementation dependencies. >= (U->) [D->] #include <klib/limits.h> // for OFF_NIOSZ_MAX and other limits.
The maximum number of preferred IO sizes is a system limit.
<Off limits. >= const int OFF_NIOSZ_MAX = 4; // Max. number of IO port sizes.
DefinesOFF_NIOSZ_MAX(links are to index).
Each IOPort maintains the information needed about each IO port
present in the system: a reference to the container the IO port is in,
and also that state kept by every system hardware resource unit,
including the magic number, reference counter, protection, domain, and
resource id.
<Off IO port. >= (U->)
// An IO port
//
class off_IOPort : public off_HWResUnit {
private:
<Other private members of off_IOPort. >
protected:
off_IOBank *io_bank; // Container
<Other protected methods of off_IOPort. >
public:
// Returns the container this io port is it.
off_IOBank *get_iobank(void) const { return io_bank; }
<Other public methods of off_IOPort. >
};
Definesoff_IOPort(links are to index).
<Off IO port dependencies. >= (U->) [D->] #include <klib/HWResUnit.h> // for off_HWResUnit class off_IOBank; // to break the circular dependency
IO ports are created with this constructor,
<Other public methods of off_IOPort. >= (<-U) [D->]
off_IOPort(const off_Protection &p, const off_prtl_id_t &domain);
although a trivial constructor is provided so that uninitialized
IOPort structures be allowed.
<Other public methods of off_IOPort. >+= (<-U) [<-D->]
off_IOPort(void) : off_HWResUnit(off_Protection(),
OFF_PRTL_NULL,
OFF_EU_ID_NULL,
OFF_MAGIC_IOPORT, NULL)
{}
<Off IO port dependencies. >+= (U->) [<-D->] #include <klib/prot.h> // for off_Protection #include <klib/ids.h> // for OFF_PRTL_NULL et al. #include <klib/Magic.h> // for magic numbers.
The real constructor only receives the protection and domain to be set
for the IO port being allocated. The magic number will always be
MAGIC_IOPORT and the identifier is generated by get_ioid.
<off_IOPort::off_IOPort implementation. >= (U->)
off_IOPort::off_IOPort(const off_Protection &p, const off_prtl_id_t &domain) :
off_HWResUnit(p,domain)
{;}
As happened to page frames, IO ports are pre-initialized too.
We have to define now the magic number for IO ports.
<Off magic numbers. >+= [<-D] OFF_MAGIC_IOPORT, // Off IO port magic number
We can now add another case to the implementation of
Magic::nameof.
<off_Magic::nameofcase form_numbers. >+= [<-D] case OFF_MAGIC_IOPORT: return "off_IOPort";
IO port users can check their references using this method.
<Other public methods of off_IOPort. >+= (<-U) [<-D->]
// Does this look like a IOPort?
boolean_t valid(void) const { return get_magic() == OFF_MAGIC_IOPORT; }
Although IO ports can have different sizes, we operate only with 8-bit
ports. When two contiguous 8-bits ports are owned, in16 can be
used in the first one to do 16-bit I/O. The same happens to 32-bit and
64-bit I/O.
<Other public methods of off_IOPort. >+= (<-U) [<-D->]
unsigned8_t in(void) const {
return off_mdepIOBank::in_b(get_id().i_offset);
}
void out(const unsigned8_t b) const {
off_mdepIOBank::out_b(get_id().i_offset,b);
}
unsigned16_t in16(void) const {
return off_mdepIOBank::in_w(get_id().i_offset);
}
void out16(const unsigned16_t b) const {
off_mdepIOBank::out_w(get_id().i_offset,b);
}
unsigned32_t in32(void) const {
return off_mdepIOBank::in_l(get_id().i_offset);
}
void out32(const unsigned32_t b) const {
off_mdepIOBank::out_l(get_id().i_offset,b);
}
unsigned64_t in64(void) const {
return off_mdepIOBank::in_q(get_id().i_offset);
}
void out64(const unsigned64_t b) {
off_mdepIOBank::out_q(get_id().i_offset,b);
}
<Off IO port dependencies. >+= (U->) [<-D->] #include <hw/mdep/mIOBank.h> // for in_b et al
\subsection{IO port allocation}
IO ports are allocated from an IOAllocator which is a subclass of
a SparseAllocator instantiated to allocate IOPorts.
<Off IO port allocator. >= (U->)
// A IO port allocator.
//
class off_IOAllocator : public off_TSparseAllocator<off_LIOPort> {
private:
<Other private members of off_IOAllocator. >
public:
<Other public methods of off_IOAllocator. >
const off_IOAllocator &operator=(const off_IOAllocator &other) {
return (*this=other);
}
};
Definesoff_IOAllocator(links are to index).
As the sparse allocator uses a liked free-list, the array nodes must
subclass DLinkedNode.
<Off linkable IO port. >= (U->)
class off_LIOPort : public DLinkedNode {
public:
off_IOPort l_io; // The IO port.
// To allow uninitialized LIOPorts
off_LIOPort(void) {;}
<Other public methods of off_LIOPort. >
};
Definesoff_LIOPort(links are to index).
An array and an indexer for LIOPorts is maintained by the
IOAllocator. An IO allocator has a fixed number of IOPorts
depending on the machine it is running on.
<Other private members of off_IOAllocator. >= (<-U)
static off_Indexer<off_LIOPort> i_idx;
static off_LIOPort i_io[OFF_MDEP_NAIOP_MAX];
<Off IO port allocator static members. >= (U->) off_Indexer<off_LIOPort> off_IOAllocator::i_idx; off_LIOPort off_IOAllocator::i_io[OFF_MDEP_NAIOP_MAX];
<Off IO port allocator dependencies. >= (U->) #include <klib/SparseAllocator.h> // for off_SparseAllocator #include <hw/mdep/mIOBank.h> // for OFF_MDEP_NIOP_MAX and OFF_MDEP_NAIOP_MAX #include <hw/IOPort.h> // for off_IOPort et al.
The allocator is initialized as follows.
<Other public methods of off_IOAllocator. >= (<-U)
// Creates an off_IOAllocator
off_IOAllocator(off_Exhausted *r) :
off_TSparseAllocator<off_LIOPort>(r,(const off_Indexable*)&i_idx,i_io,
OFF_MDEP_NAIOP_MAX,OFF_MDEP_NIOP_MAX)
{;}
The revocator will be the IO bank itself, as set at construction time.
<Initialize io_alloc. >= (<-U)
io_alloc((off_Exhausted*)this)
The notify method must thus be provided by IO banks to implement
the resource revocator signature. However, such events are silently
discarded.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
// Notifies that IO ports are exhausted.
void notify(void) {;}
Once the allocator has been initialized, the IO bank can be indexed to obtain references to IO ports and IO ports can be allocated and deallocated.
<Other public methods of off_IOBank. >+= (<-U) [<-D->]
//Gets an IO port from its distributed or physical address
off_IOPort *operator+(vm_offset_t io) {
assert(valid());
return &(io_alloc+io)->l_io;
}
// Allocates an IO port.
off_IOPort *alloc(const off_io_id_t &at, boolean_t use_revocation=FALSE);
// Deallocates an IO port.
void free( const off_IOPort *iop );
<off_IOBank::allocandfreeimplementation. >= (U->) // Allocates an IO port. off_IOPort *off_IOBank::alloc(const off_io_id_t &at, boolean_t use_revocation) { off_LIOPort *lio; assert(valid()); w_lock(); lio=io_alloc.allocate(at,use_revocation); w_unlock(); return (lio)?(&lio->l_io):(off_IOPort*)NULL; } // Deallocates an IO port. void off_IOBank::free( const off_IOPort *iop ) { off_LIOPort *lio=(off_LIOPort*)(((char*)iop)-(int)&off_LIOPort::l_io); assert(valid()); w_lock(); io_alloc.deallocate(lio); w_unlock(); }
<Off IO port dependencies. >+= (U->) [<-D->] #include <assert.h> // for assert
I/O ports are allocated and deallocated with new and delete
operators. The operator new receives the I/O bank where the port
should be allocated. It uses in turn its alloc method.
<Other public methods of off_IOPort. >+= (<-U) [<-D->]
void *operator new(size_t s, off_IOBank *iobank, const off_io_id_t &at,
boolean_t use_revocation=FALSE);
void operator delete(void *p);
<off_IOPort::operator new/delete implementation. >= (U->)
void *off_IOPort::operator new(size_t s, off_IOBank *iobank,
const off_io_id_t &at,
boolean_t use_revocation)
{
off_IOPort *io=iobank->alloc(at,use_revocation);
(void)s;
assert(iobank);
if (io) io->set_id(at);
return (void*)io;
}
void off_IOPort::operator delete(void *p)
{
off_IOPort *io=(off_IOPort*)p;
assert(io && io->valid());
((off_IOBank*)(io->get_container()))->free(io);
}
As we have seen, the reference to the container and the IO port identifier remain to be set. They are pre-initialized when the IO bank is first started.
<Start other members ofoff_IOBankaccording tomdep. >+= (<-U) [<-D] for (natural_t io=0; io < OFF_MDEP_NAIOP_MAX; io++) { off_io_id_t id(get_id(),io); (void) new(io_alloc.real_add(io)) off_LIOPort(id,this); }
<Other public methods of off_LIOPort. >= (<-U) [D->]
void *operator new(size_t s, off_LIOPort *io) { (void)s; return (void*)io;}
We used again a new operator and a pre-initialization constructor
as we did with page frames.
<Other public methods of off_LIOPort. >+= (<-U) [<-D]
off_LIOPort(const off_io_id_t &id, off_IOBank *c) : l_io(id,c) {;}
<Other public methods of off_IOPort. >+= (<-U) [<-D->]
off_IOPort(const off_io_id_t &id, off_IOBank *c) :
off_HWResUnit(id,OFF_MAGIC_IOPORT,(off_HWCompResource*)c)
{;}
\subsection{IO Bank navigation and inspection}