OSDev.org

The Place to Start for Operating System Developers
It is currently Mon Sep 23, 2019 11:34 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 55 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
 Post subject: Configuring Intel HDA
PostPosted: Sun Jun 23, 2019 6:35 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
Hello all,

I'm currently writing an OS (in rust of all things) and its going well so far, though I've ran into some hard-to-solve quandaries.
My OS currently has no audio driver, and while that's fine and all I'd like to at least get some kind of sound working. Right now I'm using QEMU tied to a serial port to stdio to build and read the output from my OS (since I'm visually impaired), however I'd love to get some actual noise. I've tried the PC speaker method over at https://wiki.osdev.org/PC_Speaker, but didn't get anything, even after programming the RTC and using that as my central timing subsystem. Next I tried the SB16, but I'm having issues with interrupts (my interrupt handlers are placed in a single file, and I doubt its safe to change IDT entries after I've loaded the IDT, since I'd then need to reload it again). So now I'm trying to get HDA working with even something as simple as a beep without interrupt handling. My OS detects the audio controller:
Quote:
PCI: probe: found Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) High Definition Audio Controller (Multimedia controller)

My question is, how do I actually configure it for audio playback? The wiki article was extremely vague and confusing, and searching the web to try and find something (bar the HdA spec itself) yielded nothing other than stuff that already required an existing OS to run, which wouldn't help. Reading the HDA spec is possible, but my PDF reader renders figures and tables in a way that's hard to interpret as a table (and tends to throw rows and columns all over the place).
I've tried looking through the Linux kernel code, as I thought that might be helpful, but it turned out not to be helpful this time. :(
Edit: updated and modified my issues paragraph and added the one above this one.


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Mon Jun 24, 2019 12:24 pm 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2010 4:53 pm
Posts: 1032
Location: Scotland
Ethin wrote:
The wiki article was extremely vague and confusing...

Do you mean this one: https://wiki.osdev.org/Intel_High_Definition_Audio ?

I wrote most of the text on that page, though the incomplete tables were put there by the original builder of the page and aren't greatly useful, but I left them there to be extended by anyone who's keen enough to do that job (and they were subsequently extended a bit by someone). In the meantime, you'll need to find a way to work from the tables in the HDA spec. As you have a visual impairment and rely on software that can't navigate the spec's tables adequately, I would like to try to help you get the information you need so that you can get your device driver written. And yes, it is fully possible to get sound in and out without using interrupts.

If you find the wiki page vague and confusing, then work with me to improve it. I wrote it in order to make navigating the spec easier (because it took a lot of repeated reading through it before I could work out how it ever got to the point where you could send the actual audio data to it or get any back in from a microphone - it is not designed to be easy to understand). By the time I could follow the spec well enough to write a guide to it, I was unable to get my mind back into a state of knowing nothing in order to write a perfect introduction to it for a newcomer with no knowledge of it at all, so I've been waiting ever since for feedback from anyone who needed clarification. Let's discuss it and knock it into proper shape.

Bear in mind that HDA is a complex device which varies widely in form, and operating systems like Windows simply get the card manufacturers to provide drivers for each specific card rather than using a universal driver capable of solving the puzzle as to how all the widgets are arranged (and which kinds exist - manufacturers are good at finding radically different ways to provide the same functionality). The BIOS for specific machines likewise has to be programmed to handle its beeps through the specific card installed in the machine. You will thus ideally want to write something more complex than a normal HDA driver as you'll want it to be adaptable enough to work on machines with different HDA cards in them in case you suddenly have to switch to a new machine and lose your audio interface which you may have come to rely on heavily by that time, but you could get something working sooner if you write a driver for your specific sound card. The vendor ID and device ID numbers are probably 8086 and 2668, but I haven't managed to find a datasheet for it, so we may just have to interrogate the device for its widget list and draw up a map to guide the writing of a simple driver specific to that card.

_________________
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Mon Jun 24, 2019 12:53 pm 
Offline
Member
Member

Joined: Sat Mar 10, 2018 10:16 am
Posts: 132
1) PC speaker
If you use QEMU, you must start it with argument -soundhw pcskp other PC speaker will not work. Bochs can have problems with PC speaker and I am dont recommend it. But PC speaker isnt very good idea for sound in OS(only if you want very little os with little functions).

2) Intel HD Audio
I am going to write this driver now too, so I cant say complete informations. But my starting points are AC97 and Týndur. On AC97 you can learn some of basic things about programming audio and týndur have hdaudio driver with very better readability as linux. Mayby it can help you.

http://www.lowlevel.eu/wiki/AC97 (MANY errors! If you want seriously deal with it, contact me and I will send you my driver, it isnt complet but better than nothing)

https://git.tyndur.org/lowlevel/tyndur/tree/master/src/modules/cdi/hdaudio


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Mon Jun 24, 2019 6:45 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
DavidCooper wrote:
Ethin wrote:
The wiki article was extremely vague and confusing...

Do you mean this one: https://wiki.osdev.org/Intel_High_Definition_Audio ?

I wrote most of the text on that page, though the incomplete tables were put there by the original builder of the page and aren't greatly useful, but I left them there to be extended by anyone who's keen enough to do that job (and they were subsequently extended a bit by someone). In the meantime, you'll need to find a way to work from the tables in the HDA spec. As you have a visual impairment and rely on software that can't navigate the spec's tables adequately, I would like to try to help you get the information you need so that you can get your device driver written. And yes, it is fully possible to get sound in and out without using interrupts.

If you find the wiki page vague and confusing, then work with me to improve it. I wrote it in order to make navigating the spec easier (because it took a lot of repeated reading through it before I could work out how it ever got to the point where you could send the actual audio data to it or get any back in from a microphone - it is not designed to be easy to understand). By the time I could follow the spec well enough to write a guide to it, I was unable to get my mind back into a state of knowing nothing in order to write a perfect introduction to it for a newcomer with no knowledge of it at all, so I've been waiting ever since for feedback from anyone who needed clarification. Let's discuss it and knock it into proper shape.

Bear in mind that HDA is a complex device which varies widely in form, and operating systems like Windows simply get the card manufacturers to provide drivers for each specific card rather than using a universal driver capable of solving the puzzle as to how all the widgets are arranged (and which kinds exist - manufacturers are good at finding radically different ways to provide the same functionality). The BIOS for specific machines likewise has to be programmed to handle its beeps through the specific card installed in the machine. You will thus ideally want to write something more complex than a normal HDA driver as you'll want it to be adaptable enough to work on machines with different HDA cards in them in case you suddenly have to switch to a new machine and lose your audio interface which you may have come to rely on heavily by that time, but you could get something working sooner if you write a driver for your specific sound card. The vendor ID and device ID numbers are probably 8086 and 2668, but I haven't managed to find a datasheet for it, so we may just have to interrogate the device for its widget list and draw up a map to guide the writing of a simple driver specific to that card.

Yeah, the wiki page is exactly what I'm talking about. I may be able to use interrupts if its possible to do something like 'm doing with my keyboard driver. What I do with that is I have a key queue, command queue and resend queue, all of which are used independently. The command and resend queues work together; every PIC timer tick the queue is read to determine if a command is ready to be sent and, if so, it is sent and removed from the queue. If the keyboard requires a resend, the resend queue holds the last command queued as well, so I just fetch from that and send it on its way. My interrupts module will notify the keyboard driver on an ack, resend, self test success/failure, key error, etc., by calling appropriate functions in the driver. If I could do the same with HDA that would be awesome since the interface would be as asynchronous as you can get without actual threads.
The first issue I'm having with even the simplest tasks with PCI (since that's what would be required to configure it) is sending commands to the device to (say) initiate a BIST if available. As an example, my is_bist_capable() function in my PCI module looks like this:
Code:
fn is_bist_capable(bus: u16, device: u16, function: u16) -> bool {
    read_word(bus, device, function, 0x15).get_bit(7)
}

Or in C:
Code:
int is_bist_capable(uint16_t bus, uint16_t device, uint16_t function) {
return (read_word(bus, device, function, 0x15) & (1 << 7));
}

I hope I got the offsets right; I just ORed the register number and offsets together, since I'm not sure of how to actually calculate the offset, and ORing them seemed to yield the right results (when getting the vendor ID for example).
I'm still confused on how to write to the device to initialize it and control it; The PCI article on the wiki gives multiple base memory addresses, and I don't know which one to read from. Would I read from all of them?
What else do you need me to provide you? If I can get passed this hurtle, things will become much easier because I can abstract that in my PCI module.


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Mon Jun 24, 2019 10:36 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 241
Ethin wrote:
I'm still confused on how to write to the device to initialize it and control it; The PCI article on the wiki gives multiple base memory addresses, and I don't know which one to read from. Would I read from all of them?


I don't quite get you. But, here goes. Every PCI function (because some devices are multi-function) has a standard configuration space of 256 bytes, and an extended configuration space of 4096 bytes. Both can only be accessed in units of 32 bits; if you want something else, read a word and isolate the halfword or byte you require.

There is the legacy way of reading and writing the standard configuration space: You write the bus, device, function, and register to one IO address and then operate on another address.

Code:
#define CONFIG_ADDRESS  0xcf8   /* and it is always these two */
#define CONFIG_DATA     0xcfc

static uint32_t pci_legacy_readreg(uint32_t bdf, uint8_t reg)
{
    outl(0x80000000 | (bdf << 8) | reg, CONFIG_ADDRESS);
    return inl(CONFIG_DATA);
}

static void pci_legacy_writereg(uint32_t bdf, uint8_t reg, uint32_t val)
{
    outl(0x80000000 | (bdf << 8) | reg, CONFIG_ADDRESS);
    outl(val, CONFIG_DATA);
}


And then there is the MMConfig way: If you have the MCFG ACPI table, you can read the base address out of it. And then access it like this:

Code:
static volatile uint32_t *virtbase;

static uint32_t pci_mmcfg_readreg(uint32_t bdf, uint8_t reg)
{
    return virtbase[(bdf << 10) | (reg >> 2)];
}

static uint32_t pci_mmcfg_writereg(uint32_t bdf, uint8_t reg, uint32_t val)
{
    virtbase[(bdf << 10) | (reg >> 2)] = val;
}


"virtbase" is the virtual mapping of 256 MB after the physical start address, set to uncachable, BTW.

This second method also allows you to access the extended configuration space. Other than that, and possibly speed, the two methods are equivalent.

But, MMConfig has one major advantage: Different drivers can use it concurrently. With the legacy method, you have to employ some method of synchronization to keep different drivers from running over each other (since address selection and data transfer are two different operations).


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Mon Jun 24, 2019 10:58 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
What I meant was, since there are multiple base memory addresses, how would I know which one to write to? By that I mean do I write to the memory address specified by register 04, offset 10, the one at register 05, offset 14, etc?
I'm working on getting ACPI in, but that will probably take a while while I do my best to comprehend the ACPI spec. :)


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Tue Jun 25, 2019 12:58 pm 
Offline
Member
Member

Joined: Sat Mar 10, 2018 10:16 am
Posts: 132
Simplified, PCI "right" offsets are only 0, 4, 8, 12, 16, 20... Then(simplified too) you can write only to registers with offset 0, 4, 8, 12, 16, 20... e.g. Command(offset 0x04) or Interrupt Line(offset 0x3C). Offset 10 is nonsens. But I think that best way for you is study some PCI driver and understand PCI from it, no from verbal explanation(e.g. my operation system LightningOS is open for you with PCI driver -> http://os-development.000webhostapp.com/lightningos.html)

//edit:
About BARs, you must know how their using device whose is connected on it.


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Tue Jun 25, 2019 3:42 pm 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2010 4:53 pm
Posts: 1032
Location: Scotland
Ethin wrote:
What I meant was, since there are multiple base memory addresses, how would I know which one to write to? By that I mean do I write to the memory address specified by register 04, offset 10, the one at register 05, offset 14, etc?

The base address you need should be at offset 10. If its type is 2 (and it probably will be - this value is found in bits 1 and 2), then the next base address (at 14) is an extension of the first base address, giving you a 64-bit address instead of a 32-bit address. In most machines that extension to the address will be zero so that the machine can run a 32-bit mode OS and access the memory mapped ports within that address space, but this may change in the future once everything is 64-bit only. All you need to do then is collect that 32-bit base address and use it to find the memory mapped ports for controlling the HDA device, but you have to mask out the lowest four bits first by ANDing it with FFFFFFF0h.

_________________
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Tue Jun 25, 2019 5:08 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
DavidCooper wrote:
Ethin wrote:
What I meant was, since there are multiple base memory addresses, how would I know which one to write to? By that I mean do I write to the memory address specified by register 04, offset 10, the one at register 05, offset 14, etc?

The base address you need should be at offset 10. If its type is 2 (and it probably will be - this value is found in bits 1 and 2), then the next base address (at 14) is an extension of the first base address, giving you a 64-bit address instead of a 32-bit address. In most machines that extension to the address will be zero so that the machine can run a 32-bit mode OS and access the memory mapped ports within that address space, but this may change in the future once everything is 64-bit only. All you need to do then is collect that 32-bit base address and use it to find the memory mapped ports for controlling the HDA device, but you have to mask out the lowest four bits first by ANDing it with FFFFFFF0h.

So, to confirm, for 64-bit addresses, I would:

1) Get the first BAR (BAR 0)
2) Examine its type
3) If type is 1 or 2, get second BAR (BAR 1) and mask the first four bits by ANDing it with FFFFFFF0h.
4) If type is 0, mask BAR 0 with FFFFFFF0h.
Do I have that right?


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Wed Jun 26, 2019 4:28 pm 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2010 4:53 pm
Posts: 1032
Location: Scotland
Ethin wrote:
So, to confirm, for 64-bit addresses, I would:

1) Get the first BAR (BAR 0)
2) Examine its type
3) If type is 1 or 2, get second BAR (BAR 1) and mask the first four bits by ANDing it with FFFFFFF0h.
4) If type is 0, mask BAR 0 with FFFFFFF0h.
Do I have that right?

When you get BAR 0, that will usually contain the 32-bit base address for the HDA's memory mapped ports once you've ANDed it with FFFFFFF0h. The type field in BAR 0 may be 0 or 2. If it's 0, then you don't need to look at BAR 1 at all. If the type field in BAR 0 is 2 though, then BAR 1 contains the extension to the address to turn it into a 64-bit address (and BAR 1 does not need to be ANDed with anything), but you'll likely find BAR 1 to be zero anyway because the ports are usually mapped within the 4GB address range available from 32-bit protected mode, in which case you can again ignore it. You cannot assume that BAR 1 is zero though, so if you find that it isn't and that the type field of BAR 0 is 2, then HDA can only be controlled from long mode (or by remapping the ports). It's highly unlikely that you'll find a machine like that today, but maybe in the future it will become the norm because it will be assumed that operating systems will run in long mode, even if the machine is still capable of running in protected mode too. (There might also be two separate HDA cards in some exotic machines with one accessible from 32-bit mode and the other one not, so again you should always make the necessary checks and not rely on assumptions.)

_________________
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Wed Jun 26, 2019 8:44 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
OK, I think I'm getting the header type incorrectly. I'm trying to just get the base address of the HDA device and its returning 0 for both masked and unmasked addresses. I then had it print all six BARs and they were all zeros. Here's how I'm getting the BARs:
Code:
                            bar0: read_word(bus, slot, function, 0x04|0x10).get_bits(24..31),
                            bar1: read_word(bus, slot, function, 0x05|0x14).get_bits(24..31),
                            bar2: read_word(bus, slot, function, 0x06 | 0x18).get_bits(24..31),
                            bar3: read_word(bus, slot, function, 0x07 | 0x1C).get_bits(24..31),
                            bar4: read_word(bus, slot, function, 0x08 | 0x20).get_bits(24..31),
                            bar5: read_word(bus, slot, function, 0x09 | 0x24).get_bits(24..31),

This is rust code, but the important part is the offset -- the fourth argument to read_word(). As you can see, I'm ORing the register and offset fields of the table in the PCI article, since (at the time) when I was testing it it seemed to generate proper results. Apparently this is not the way to do it, or my PCI enumeration routine is messing up somewhere. I'm getting the header type:
Code:
read_word(bus, device, function, 0x03|0x0C).get_bits(16..23)

Is this the right way to calculate offsets? Or am I doing it wrong?


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Thu Jun 27, 2019 10:39 am 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2010 4:53 pm
Posts: 1032
Location: Scotland
Ethin wrote:
This is rust code,

I don't know rust, so someone who does needs to check it.

Quote:
but the important part is the offset -- the fourth argument to read_word(). As you can see, I'm ORing the register and offset fields of the table in the PCI article, since (at the time) when I was testing it it seemed to generate proper results.

Offsets should be added rather than using OR. I don't know what you're ORing the offsets with though, because the register number should be the unmodified offset value. I think when you try to read BAR 0, you're actually reading BAR 1 because you've accidentally added 4 to it, and then when you try to read BAR 1, you're reading three bytes of BAR 2 and one from BAR 3, etc.

Could you try collecting offset 8 for me, (to get 8, 9, A and B) because I want to know what fields A and B are (just to make sure that this is the HDA device).

_________________
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Thu Jun 27, 2019 12:52 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 56
DavidCooper wrote:
Ethin wrote:
This is rust code,

I don't know rust, so someone who does needs to check it.

Quote:
but the important part is the offset -- the fourth argument to read_word(). As you can see, I'm ORing the register and offset fields of the table in the PCI article, since (at the time) when I was testing it it seemed to generate proper results.

Offsets should be added rather than using OR. I don't know what you're ORing the offsets with though, because the register number should be the unmodified offset value. I think when you try to read BAR 0, you're actually reading BAR 1 because you've accidentally added 4 to it, and then when you try to read BAR 1, you're reading three bytes of BAR 2 and one from BAR 3, etc.

Could you try collecting offset 8 for me, (to get 8, 9, A and B) because I want to know what fields A and B are (just to make sure that this is the HDA device).

I'm quite confused by what you mean. Are you saying that I should use the register number as the offset that I pass to read_word(), the offset value in the offset table, or should I add the two together (register plus offset)? I was ORing the offset and register together (register | offset). In either case, passing the offset listed in the table, adding the register and offset, and ORing them all produce the same result. I'm confused by what you mean when you ask me to collect offset 8 too. I'm sorry, I'm just majorly confused on how I should determine the offset to use. The read_word function in rust that I have looks like:
Code:
pub fn read_word(bus: u16, slot: u16, func: u16, offset: u16) -> u32 {
    let mut address = 0u32;
    let lbus = bus as u32;
    let lslot = slot as u32;
    let lfunc = func as u32;
    let mut tmp: u32 = 0;
    address = (((lbus << 16) as u32)
        | ((lslot << 11) as u32)
        | ((lfunc << 8) as u32)
        | ((offset as u32) & 0xfc)
        | (0x80000000)) as u32;
    unsafe {
        outl(address, 0xCF8);
        tmp = inl(0xCFC);
        tmp = tmp >> ((offset & 2) * 8) & 0xFFFF;
    }
    tmp
}

Which is pretty much equivalent to the C code you provided in the PCI article, i.e.: this code:
Code:
uint16_t pciConfigReadWord (uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset) {
    uint32_t address;
    uint32_t lbus  = (uint32_t)bus;
    uint32_t lslot = (uint32_t)slot;
    uint32_t lfunc = (uint32_t)func;
    uint16_t tmp = 0;
 
    /* create configuration address as per Figure 1 */
    address = (uint32_t)((lbus << 16) | (lslot << 11) |
              (lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
 
    /* write out the address */
    outl(0xCF8, address);
    /* read in the data */
    /* (offset & 2) * 8) = 0 will choose the first word of the 32 bits register */
    tmp = (uint16_t)((inl(0xCFC) >> ((offset & 2) * 8)) & 0xffff);
    return (tmp);
}


Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Thu Jun 27, 2019 1:42 pm 
Offline
Member
Member

Joined: Sat Mar 10, 2018 10:16 am
Posts: 132
Ethin, I was confused with PCI as you too, but I now have a solution. See this code(from my driver):

Code:
deleted (if you want this code repeat, write me)


Its quite different of "normal" PCI drivers e.g. on PCI article. But it works, if you know how it use. You can use it:
Code:
deleted (if you want this code repeat, write me)


Did you see the idea and how PCI works? And for BARs, you can get port BAR with(its simplified, not included 64 BAR memory address):
Code:
deleted (if you want this code repeat, write me)


I hope that this help.


Last edited by Klakap on Thu Jul 18, 2019 5:56 am, edited 2 times in total.

Top
 Profile  
 
 Post subject: Re: Configuring Intel HDA
PostPosted: Thu Jun 27, 2019 2:23 pm 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2010 4:53 pm
Posts: 1032
Location: Scotland
When you're using the two PCI ports to access a table for a device, you send a 32-bit value to one of those ports to select which part of the table you wish to read through the other port and that part of the table will be placed there for you to read. The value that you send to the first port has the value 0 in its least significant byte (bits 7 to 0) when you're trying to read the first table entry (at offset 0). To access the next four bytes of the table, the least significant byte of the 32-bit value that you send to the port will be 4 instead of 0. That is the only thing you need to change: the value of that byte is always the offset for the entry that you want to collect. To access the four bytes at offset 8, the least significant byte of the value you send will be 8. That's how you access all the offsets. If you want offset 10, you put 10h in that byte. You should build a 32-bit base value to send to the port each time (which you only need to build once because you want the same bus number, device number and function number every time), then you add the required offset to that base value each time. Once you've built that base value, it's that simple.

_________________
Help the people of Laos by liking - https://www.facebook.com/TheSBInitiative/?ref=py_c

MSB-OS: http://www.magicschoolbook.com/computing/os-project - direct machine code programming


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 55 posts ]  Go to page 1, 2, 3, 4  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 9 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group