Help - Search - Members - Calendar
Full Version: New ATI Framebuffer Xcode project to start from
InsanelyMac Forum > InsanelyMac Lounge > The X Labs > ATI Framebuffer
Pages: 1, 2, 3
dong
As we all known, native ATI drivers are named as ATY_Wormy, ATY_Caretta, etc. Following that, I construct a new Xcode project named ATY_HD, and put in it preliminary stuffs that I learn from dissembled native drivers.

ATY_HD now can compile and work, but only with the same function of IONDRVSupport. You may need modify "OSBundleRequired" part in plist file to fit for your system version.

One extra is that I combined the natit like thing into it, that means it can inject required entries into IORegistry by itself and then ATY_HD is attached to IONDRVDevice just like native driver do. However, this behavior is not so important at this time as ATY_HD is not a fully functional driver yet.

Another thing is that I defined a DriverGlobal structure in ATY_Struct.h, anyone interested in porting linux code can put their shared structures into it, thus provide a way to transport them between all functions.

Also I define some Debug Macros people may found useful in ATY_Struct.h. I'm now using it to check what cmd system sends to my driver.

In the plist file, "Personality Injection" represents the natit part, I put in a "IOPCIMatch" to limit the side-effect of the driver to other native drivers. So just change its value into your device id to start working with it.
samm5506
I'm sort of new to this stuff so sorry if this sounds dumb, but will this work for any ATI HD gpu's? Such as my ATI Radeon HD 2900 XT? (Which I can't seem to find any working drivers for atm) :/
dong
QUOTE (samm5506 @ Mar 5 2009, 09:45 PM) *
I'm sort of new to this stuff so sorry if this sounds dumb, but will this work for any ATI HD gpu's? Such as my ATI Radeon HD 2900 XT? (Which I can't seem to find any working drivers for atm) :/


The goal is to make ATY_HD work for Radeon HD, but not now. If IONDRVSupport(VESA) does not work for you, ATY_HD currently won't either.
Now it is only a start point with basic functions as IONDRVSupport. I need to figure more out from either Linux codes or dissembled native driver code to add to it.
At the mean time, I put the code here in case any other developers are interested in this.
Slice
Great dong!
Wish you be successful. If any question I am here.
My laptop with ATI is dead so I am out of the problem now.
Boombeng
Isn't it the project we, all radeon mobility HD users, were waiting for ?!
Thank you Dong! I'm sad I don't have knowledge to help...
rschultz101
great! for laptop ATI mobility !

would need source code for ATI .
would be great, to have native open osx drivers,
not to rely on osx compiled versions.

be even great to have it for nvidia .

but think, it's to big of a project,
and be to hard to get the sourcecode officially,...

at least a working wrapper be good

wish I could help, but I'am not a coder
cybercow
Hello to all brave devs here, i`m kinda new here, but i think i readed every line of infinite posts about ati cards especially on laptops with mobility series in which topic i`m interested though because i`m the owner of one. When i come here the first time i though things will be more easy and i don`t understood very well the main problematics why the hell my ati card not works, and was very magnetic to posts like "help me my card not works !!!" or "Finally the solution for card XXXX !!!". When i saw things will not go that way with forced applying this and that, jumping from one post to another, rebooting my laptop for the "last time" and praying god that finally boot in native resolution i decided to stop and think a litlle.

- i have the HP 6820s with mobility x1350, the "damned" one. Ok, i know is "old" i know nothing is supported bellow x1600, and i readed many times people encourage others to sell their uncompatible hardware as an easy way to get things up. Ok i can do that, i can even think my HP is a piece of obsoleted hardware, but ,,,,, i swear i see many people happily running their original G4`s at 700 Mhz, they know that this hw is "old", but still they re using it because it sodisfy their needs ... well same is here, i can sell my HP and get another one, and i`m sure many other users can do that, but they don`t do that - because they re happy with it, same as i`m happy with my HP. If i wanted to do advanced 3D stuff or gaming on a portable, i will choose another model in the first place either ....

- ok, so i`ll stop talking to not spam this post unnecessarly .... what i wanted to tell and ask .... i`m new to OSx and OSx developing in C/C++, but i have some development background in other languages/platforms enough to understand the logic of things.

- not related with this new stuff - i downloaded the decompiled version of Caretta someone posted, i opened the code, there is ~300K of code and few header files, someone remarked the known points in the code blocks. I would ask why using this with ATIInject was an dead end ? I understood the LVDS problematics, the EDID problematics though... does anyone try to compile this decompiled version and integrate it with new features added ? where it resides on the system ?

- I know i`m asking stupid questions, but i want to learn, and even help to do some nice stuff, i started to use Xcode and explore this all new world to me.... I will try to download and compile the ATY_HD here posted and hope to get some results that will help though. Can i try it on my mobility - or is only for HD series ?

Cheers to all and good luck
dong
The dissembled Carreta code is posted by me, so I can say something here.

When you trying to dissemble the binary code, the most difficult thing is figure out the data structure it has used because all the meaningful names are lost during compiling and it's hard to differentiate struct type data from array or many separate single ones. For a device driver, there are so many structs involved and you don't have a pre-knowledge what they might be since we are not from ATI. To have a rough idea how complex this could be, you can just take a look of the source code of the open linux radeonHD driver on http://www.x.org/wiki/radeonhd.

What I mean above is simply that the code I dissembled contains many mistakes in it. When you try to use it directly, you have to recheck with the binary code to make sure it does not interpret wrong, that's what I'm trying to do now.
Also, that code is not complete, lacking definitions of many structs or variables, and some low level hardware handling functions are not dissembled yet.

If you are brave enough to pick up all this, you have to learn some reverse engineering knowledge.

As for ATY_HD I posted here, like what I have said, it's only a start point without any extra function for any ATI cards including HD series. You can use it for your purpose for any cards if only you add in the required codes.

You may want to know, ATI mobility x1350 is actually one of the HD cards, thus you may borrow codes from the linux driver.
lebidou
Hello,

As a laptop user, I'm interested in an Open ATi framebuffer because atm I'm stuck with Tiger's ATINDRV.kext and I'm not sure it's going to work with possible future updates. I'm not a coder, but I have enough notions to decrypt a c or cpp file.

So, I was thinking about the LVDS and EDID thing. I looked in my EDID to see precisely what informations about my internal panel it contains. In fact, it doesn't contain any info about the different resolutions I have access to, exept the native one (1280x800).

So I downloaded RadeonDump and compiled AtomDis to look for informations about those modes in the atombios. There is informations about the panel (Horizontal/Vertical Active/Blankig Pixels,Pixel Clock…) but nothing about the other resolutions, I only found infos for the native one.

The only "useful" informations in the EDID are color infos, the vendor/product id and the actual names of the manufacturer and the product. So maybe getting the EDID for the internal panel isn't to important after all, A bios dump/parse would be enough to make a fake one.

But a question remains, where does my various resolutions and "DetailedTimings" in IOReg come from ? Are they automatically computed by the framebuffer ?

Maybe I missed somthing…

Here are the various resolutions I have :


Note : My ATI Mobility X1900 is a replacement card, when I had a nVidia 7600 Go I hadn't the 800x500 and the 1024x640 modes, they appeared with the ATi.
dong
sounds like automatically computed by the framebuffer. And you notice that all other resolutions are universal VESA ones.
cybercow
Hello dong, thank you much for your reply, i really appreciate your hard work on ATI field, i think i readed every post of yours in this field with great enthusiasm, u give nice ideas and hope to the rest of us with some kind of bastarduos ATI hardware. I have few more questions for you and your ATY_HD project.

1. It would be nice if u can write 1 page of simple dirrections, what is done, what is still to do and how to use the whole thing. I know that some "pro" will catch the whole thing in a minute, and that you already wrote here how to do it, but still having one simple *.txt description page would be nice to resume all of your results.

2. btw can this -> VideoHardwareInfo also attached be of some help ?

[edit -> i compiled ATY_HD today, and it worked for me (HP 6820s) -> i attached screen pic and syslog part if it helps dunno]

besides this i also attached the hp6820s edid dump if someone needs it [not related for now]


thank you in advance
0xdeadbeef
Hi dong,

Very interested in the porting work you did to xf86-video-radeonhd. I have done a lot of work looking through Iago for my HD2400M and after tweaking your code for RadeonHD, I actually got my internal display to switch on and provide a garbled display. ( "first time ever" yay!) thumbsup_anim.gif However using the accelerator failed on set_display_mode_and_vram() Which tells me something about what the accelerator is doing.

I have a feeling the reason the linux driver wasn't more successful on OSX was the lack of DDC communication - this is what breaks most of the apple drivers too. Many Laptops have no DDC on their internal display and even on desktop cards the DDC channels can be switched around compared to apples. If we get the linux AtomBIOS parser working, we can at least make an effort at constructing a viable display structure + connector info for displays that have no DDC. I have tried quite hard to get the xf86 AtomBIOS parser to compile, but there are very many problems yet.

OSX doesn't actually require a framebuffer to be very complex, if the correct display structures are returned and set in the corresponding doDriverIO() for a couple of functions, that is all the OS needs to know. However it is unlikely such a framebuffer would work in conjunction with the ATI accelerator - there are function codes in the ATI framebuffers for things like "DoATICommunication()" etc which might need to be implemented as well.

Are you going to be deriving this driver from xf86 too, or building it up manually?
dong
Frankly say, I kind of stuck into the dissembling of ATY_Carreta and ATY_Wormy when try to put the native initialize codes into ATY_HD and make it working. Maybe I should first look at DoATICommunication().
0xdeadbeef
QUOTE (dong @ Mar 13 2009, 02:05 AM) *
Frankly say, I kind of stuck into the dissembling of ATY_Carreta and ATY_Wormy when try to put the native initialize codes into ATY_HD and make it working. Maybe I should first look at DoATICommunication().


I don't think so. A framebuffer should just provide information to the OS about the supported resolutions, and connected displays called through the subclass to IONDRV, it shouldn't need the accelerator to work to provide that information. What I mean is the interface to the native framebuffer only needs to implement the IONDrvFramebuffer calls to be useable by the OS for resolution change, gamma, detect display EDID, etc. the CoreImage and IOAccelerator stuff shouldn't be needed for that, unless I am very much mistaken. So there might be very much hardware/card specific code to get the card setup and started, but no hardware specific communications with the OS Graphics subsystem.
dong
Well, all stuffs in ATY_Carreta and ATY_Wormy are related to resolution change, gamma, detect display EDID, etc. The CoreImage and IOAccelerator stuffs are in other kexts. The problem here is how to get the hardware/card specific code to get the card setup and started.
Slice
QUOTE (0xdeadbeef @ Mar 13 2009, 03:30 AM) *
However using the accelerator failed on set_display_mode_and_vram() Which tells me something about what the accelerator is doing.

The accelerator driver (ATIRadeonX1000.kext) use the method set_display_mode_and_vram() to initialize internal variables and GPU registers with information calculated from register 0x00f8 that filled by BIOS. Correction of the register is my key setting in ATILead.kext (previously ATISlice).
lebidou
I've done more searching on my side, and as I thought, building a fake EDID with AtomBios informations gives enough information to the "official" framebuffer.
I've modified ATILead to dump these infos (using AtomDis code), build an EDID string and inject it as "LVDS,EDID".
However as there is no color information it brokes ColorSync.

Can't be a workaround for non DDC internal panels ? I've seen something in the xf86 radeon hd driver about fake edid and the sLVDSInfos structure, so maybe it is what it uses to.

I'll make the code nicer and post it if you want to.
0xdeadbeef
QUOTE (dong @ Mar 13 2009, 03:36 AM) *
Well, all stuffs in ATY_Carreta and ATY_Wormy are related to resolution change, gamma, detect display EDID, etc. The CoreImage and IOAccelerator stuffs are in other kexts. The problem here is how to get the hardware/card specific code to get the card setup and started.



Indeed, but I think the linux driver does all this quite well. For me at least, the biggest problem with the ATY_* framebuffers has been figuring out the mappings from display->transducer->CRTC->connector. The ATY_* framebuffers do most of this automatically, going through CheckLVDSConnection(), CheckDACConnection(), CheckTMDSConnection() etc etc on startup, but if the DDC line fails, it has problems and must rely totally on the _DriversConnectors[] tables - i don't think it parses AtomBIOS at all. However the latest ATY_* framebuffers (ATY_Quail and ATY_Motmot) do parse AtomBIOS on startup (I don't know yet if they read connector+detailed timing info from the bios).

The biggest change framebuffer-wise between the R500->R600 drivers appears to have been the 1-register shift for initializing the likes of TMDS2 (LVDS) I had thought of trying to patch all the register offsets in Wormy to try to get it to work with my R600, but I realised I would probably just end up making an Iago blink.gif

Would it be very difficult, you think using the xf86 code to start the card and mapping the structures on-the-fly in DriverIO so that we can extract resolution, edid and gamma info from the running linux driver through the pScrn and returning it to IOGraphics? And the corresponding setResolution & gamma functions too?
0xdeadbeef
QUOTE (lebidou @ Mar 13 2009, 12:05 PM) *
I've done more searching on my side, and as I thought, building a fake EDID with AtomBios informations gives enough information to the "official" framebuffer.



This doesn't usually work on the R600 framebuffers for laptop internal displays ( it is possible to inject your real display EDID with Natit ). the problem is that the R600 framebuffers like Iago will not bring up the LVDS connection even if you do that.

There are other settings you can use to inject EDID - ( "override-no-connect" is one of them) if you put your EDID in there, the framebuffer will read your display EDID and try to construct a display according to the type you specify in "connector-type". Unfortunately Iago will still refuse to switch LVDS on
(maybe because _HW_InitLVDS() looks like this thumbsdown_anim.gif )

CODE
void __cdecl HW_InitLVDS()
{
;
}


I have tried to patch in code from Triakis' HW_InitTDMS2() and set LVDS mode without any success so far.


the override-no-connect property is read like this;

CODE
signed int __cdecl DoDDCForceRead(int a1, int a2, char a3, unsigned __int8 a4, char a5, char a6)
{
  __int16 v7; // ax@6
  char v8; // [sp+3Ch] [bp-1Ch]@1
  unsigned __int8 v9; // [sp+38h] [bp-20h]@1
  char v10; // [sp+34h] [bp-24h]@1
  char v11; // [sp+30h] [bp-28h]@1
  signed int v12; // [sp+2Ch] [bp-2Ch]@2
  char *v13; // [sp+4Ch] [bp-Ch]@6
  __int16 v14; // [sp+4Ah] [bp-Eh]@6

  v8 = a3;
  v9 = a4;
  v10 = a5;
  v11 = a6;
  if ( *(_DWORD *)(a1 + 8) & 1 )
  {
    if ( *(_DWORD *)(a1 + 4) )
    {
      v12 = -50;
    }
    else
    {
      if ( v10 & v9 )
      {
        v13 = "override-no-connect";
        v7 = ReadOverrideEDIDProperty(a2, v9, (unsigned __int8)*(_DWORD *)a1, "override-no-connect", a1 + 16);
        v14 = v7;
        if ( v7 )
          return -19;
      }
      else
      {
        DDCInit(a2);
        if ( (unsigned __int8)DDCGetEDID(a2, (unsigned __int8)*(_DWORD *)a1, a1 + 16, v9) == 1 )
        {
          if ( (unsigned __int8)EdidDigital(a2, a1 + 16) )
            v13 = "override-has-edid-digital";
          else
            v13 = "override-has-edid";
          ReadOverrideEDIDProperty(a2, v9, (unsigned __int8)*(_DWORD *)a1, v13, a1 + 16);
        }
        else
        {
          v14 = -19;
          if ( v11 & v9 )
          {
            v13 = "override-no-edid";
            v14 = ReadOverrideEDIDProperty(a2, v9, (unsigned __int8)*(_DWORD *)a1, "override-no-edid", a1 + 16);
          }
          if ( v14 )
            return -19;
        }
      }
      if ( (unsigned __int8)DDCCheckEDID(a2, a1 + 16, (unsigned __int8)((unsigned __int8)*(_DWORD *)a1 - 1), 128) == 1 )
        v12 = 0;
      else
        v12 = -19;
    }
  }
  else
  {
    v12 = -50;
  }
  return v12;
}
dong
QUOTE (0xdeadbeef @ Mar 13 2009, 08:38 AM) *
Would it be very difficult, you think using the xf86 code to start the card and mapping the structures on-the-fly in DriverIO so that we can extract resolution, edid and gamma info from the running linux driver through the pScrn and returning it to IOGraphics? And the corresponding setResolution & gamma functions too?

I think in my radeonHD project, I already managed to extract EDID and resolution (but not gamma) from the linux driver and provide the information to OS, which results the display of multiple available resolution choices in the Display Preference Panel. However, directly utilize setResolution function from linux driver failed for me. And I have no clue yet what could be the exact solution. That happened almost one year ago, and since that I'm digging and digging in the native drivers. Sadlly I'm almost lost in the thousands of lines of dissembled code, and do not have a clear mind yet to find out what I need from them.
But nice discussion with you guys, that will someday make it clear for me.

As for the DDC thing, here is my dissembled code for them that just was put in one file these days.
netkas
Hey, can u make a tiny driver which will just initialize a card and quit, without any modes settings and etc, just like what bios/efi does (without settings display mode) ?

it will help us to have multi card ati setups with apple drivers
dong
Hi, netkas,

If what you mean is a natit like thing with only the function of injecting required entries (already present in plist file) into IORegistry, it should be easy that anyone can write one.

But it won't be so easy if these entries need to be determined by checking the card at boot time as I'm not very familiar with the meanings of those entries yet. If anyone has a clue of this, we may give it a try.
netkas
Dong, no, injecting works with natit just fine, vga bios does some initialization of card (using atombios prolly) , so the card can be used by osx drivers, if no initializayion was done - osx drivers will not detect any output on card. so, atm, we cant use dual card solutions using ati.
(dual card - for 4 monitors)
Slice
Yes, I know that some initialization by BIOS did occur. But how can we know what exactly? I know a set of registers "BIOS_..._scratch", "Video_Memory_...". What else?
dong
QUOTE (netkas @ Mar 17 2009, 01:20 AM) *
Dong, no, injecting works with natit just fine, vga bios does some initialization of card (using atombios prolly) , so the card can be used by osx drivers, if no initializayion was done - osx drivers will not detect any output on card. so, atm, we cant use dual card solutions using ati.
(dual card - for 4 monitors)


Ok, I now got what you mean.

After reading some posts on the web, I got the information like that there is no such initialization problem for dual NVidia cards, but ATI cards just won't POST if plugged in as secondary card.

It looks to me that the issue here is how to get the secondary ATI card POSTed. I rechecked codes in radeonHD linux driver, they are using int10 in a simulated real mode to boot the BIOS on the secondary card, which is not so easy to port to OSX in my opinion.

And I'm a little confused here about whether just running the card BIOS got it POSTed or you have to additionaly running ATOM init functions to get job done?

Without int10, there is also another problem of accessing the video BIOS as it's not located at 0xC0000 (already used by primary card) and you got no idea where it will be (maybe not in system memory at all). Without access to the BIOS image, there is no way to call ATOM init functions.

Edit: well, I checked ATY_Megalodon and found it did provide a way to read BIOS by interacting with IO registers of the card (0x1600 - 0x1620, 0x1798 - 0x17A0 for setting card to enter/exit BIOS reading state, and 0xA8, 0xAC for actually reading, 4 bytes each time). I will test it to see if this is general for any ATI cards.

Update: I tried these registers on my mobility x1400, but not working. But I saw similar code in radeonHD (RHDReadPCIBios), it does confirm that it depends on the type of card. Some time will be needed to test with it.

Please point out if I got something wrong.
0xdeadbeef
QUOTE (dong @ Mar 17 2009, 10:50 PM) *
And I'm a little confused here about whether just running the card BIOS got it POSTed or you have to additionaly running ATOM init functions to get job done?

Without int10, there is also another problem of accessing the video BIOS as it's not located at 0xC0000 (already used by primary card) and you got no idea where it will be (maybe not in system memory at all). Without access to the BIOS image, there is no way to call ATOM init functions.



Maybe you can get all that info by calling IOACPIPlatformDevice methods:

_ROM (Get ROM data)
_GPD (Get POST Device)
_SPD (Set POST Device)

See pages 576..578 of the ACPI Spec

Although if this can be done from a display driver I have no idea. I do know that ATY_* framebuffers call IOACPIPlatformDevice->_DSS (Device Set State)
np_
here is more info

http://www.x.org/docs/AMD/
http://www.x.org/wiki/radeonhd

what you need to do is new iokit driver who loads before any ati***.kext ( something like natit )

mapping ATI mem base IO ( check docs for correct registers, they are almost same for any ati card )

and you can read/write anywhere in card mem space , that include to read EDID , card bios ..etc

getting correct info you can push it into ioregs without no problem at all

just note - avoid to hard code EDID info data into ioregs or any data copied from real mac's ioregs, it will cause in most situation black screen or even put video card in funny moods
netkas
dong, the bios you can always get from ioreg, for r6xx+ cards its there under name ATY,bin_image

and here is what radeonhd x86 does (from rhd_atombios.c)

if (unposted) {
/* run AsicInit */
if (!rhdAtomASICInit(handle))
xf86DrvMsg(scrnIndex, X_WARNING,
"%s: AsicInit failed. Won't be able to obtain in VRAM "
"FB scratch space\n",__func__);


static Bool
rhdAtomASICInit(atomBiosHandlePtr handle)
{
ASIC_INIT_PS_ALLOCATION asicInit;
AtomBiosArgRec data;

RHDFUNC(handle);

RHDAtomBiosFunc(handle->scrnIndex, handle,
GET_DEFAULT_ENGINE_CLOCK,
&data);
asicInit.sASICInitClocks.ulDefaultEngineClock = data.val / 10;/*in 10 Khz*/
RHDAtomBiosFunc(handle->scrnIndex, handle,
GET_DEFAULT_MEMORY_CLOCK,
&data);
asicInit.sASICInitClocks.ulDefaultMemoryClock = data.val / 10;/*in 10 Khz*/
data.exec.dataSpace = NULL;
data.exec.index = GetIndexIntoMasterTable(COMMAND, ASIC_Init);
data.exec.pspace = &asicInit;

xf86DrvMsg(handle->scrnIndex, X_INFO, "Calling ASIC Init\n");
atomDebugPrintPspace(handle, &data, sizeof(asicInit));
if (RHDAtomBiosFunc(handle->scrnIndex, handle,
ATOMBIOS_EXEC, &data) == ATOM_SUCCESS) {
xf86DrvMsg(handle->scrnIndex, X_INFO, "ASIC_INIT Successful\n");
return TRUE;
}
xf86DrvMsg(handle->scrnIndex, X_INFO, "ASIC_INIT Failed\n");
return FALSE;
}
netkas
ah well, framebuffer already has _atom_ASIC_Init function, it called on wakeup.
dong
netkas, I checked your natit code, you're still getting BIOS ROM from the legacy address 0xC0000 if the ROM is not present in plist file. It won't work for a secondary ATI card.

So, do you want to modify the native driver some way to make it execute atom_ASIC_Init at initialization time?

Or a tiny driver is still needed to do this job?
netkas
i get it from 0xc0000 only if binimage wasnt found in plist, that;s answer wink.gif

well. i modified motmot that way, and it's attitude changed when card run in vga mode, not vesa, but still isnt perfect.
dong
Netkas, I put rhdAtomInit related stuff together. It's compiled ok but not tested yet. If it does not do its job or causes KP for some reason, don't blame me. tongue.gif

In the probe function, I assume BIOS can be read from IOPCIDevice's "ATY,bin_image". You have to make sure Natit load before this and set that to IOPCIDevice.
Click to view attachment
Click to view attachment
Now if BIOS can not be read from "ATY,bin_image", the BIOS at legacy address will be used instead. Adding this is because I don't have a "ATY,bin_image" to debug the code.
Click to view attachment

Find it in thanoulas's post in this topic if you want to download.
netkas
seems like you forgot to add some sources to it smile.gif

kld(): Undefined symbols:
_DelayMicroseconds
_DelayMilliseconds
_ReadIndReg32
_ReadPCIReg16
_ReadPCIReg32
_ReadPCIReg8
_ReadReg32
_ReadSysIOReg16
_ReadSysIOReg32
_ReadSysIOReg8
_WriteIndReg32
_WritePCIReg16
_WritePCIReg32
_WritePCIReg8
_WriteReg32
_WriteSysIOReg16
_WriteSysIOReg32
_WriteSysIOReg8




I attached corrected info.plist, the default one doesnt work.



http://rapidshare.de/files/46265563/Info.plist.html
dong
My bad, didn't even test it with kextload. The undefined symbols is caused by atombios compiling macros. All the sources are already there. To solve this,

In CD_hw_services.h, add a line at the beginning, i.e., add
#define ENABLE_ALL_SERVICE_FUNCTIONS
right after
#include "CD_Structs.h"

You may also add the line at the beginning of hwserv_drv.c, all the function implementations are there.

The ENABLE_ALL_SERVICE_FUNCTIONS definition will enable them.

The whole project in my last post is also updated.
netkas
panics in atomSaveRegisters function
dong
QUOTE (netkas @ Mar 22 2009, 10:18 AM) *
panics in atomSaveRegisters function


Don't know exactly what caused it, possibly the memory allocation/deallocation stuff. And I do not see how useful can this function be, it seems only backup the registers each time when there is a writing but I do not find out where they are used to restore the values. So a simple suggestion to avoid this panic is to comment out all occasions of atomSaveRegisters in the code.
netkas
Sorry, i was a bit lazy to analize backtrace last time so made wrong assumption, so getting rid of saveregisters functions made no difference smile.gif

panic was in __RHDRegWrite

│ 1950 ! mov edx, eax
│ 1952 ! mov eax, [ebp+offset_10]
│ 1955 ! mov [edx], eax
│ 1957 ! leave
│ 1958 ! ret

EIP - 1955

edx - a030 , eax - 0x02
dong
Just a quick response, I suddenly remember that I forgot to add a "device->setMemoryEnable(true)" in ATY_Init.cpp. I will try to debug further by myself, but you may give another try.

Update: after adding that, it now works for me, though the screen goes into a strange state. Here is the log:
CODE
dong/project/ATY_Init/build/Debug; USER=root; COMMAND=/sbin/kextload -t ATY_Init.kext
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 97
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,16)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister© = 1ca
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 16
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,96)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(c,1ca)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 96
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,9c)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(c,1a)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 9c
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,27)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister© = 24a
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 27
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,a7)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(c,24a)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = a7
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,28)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister© = 28a
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 28
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,a8)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(c,28a)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(26) = 17
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(26,13)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(1e) = 17
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(1e,13)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(1d) = 17
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(1d,13)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(574,1)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(570) = 0
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(570,0)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(580,c000000)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(4cc,1f000000)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(cool.gif = 100fac8f
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(b,100fac8f)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(cool.gif = 100fac8f
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(b,100fac13)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = 8b
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,cool.gif
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister© = 100fac13
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(8) = b
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(8,8b)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(c,100fac8f)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadPLL(cool.gif = 100fac8f
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWritePLL(b,100fac8f)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReleaseMemory
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReadATIRegister(10) = 40000
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: atomSaveRegisters
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailWriteATIRegister(10,40000)
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: CailReleaseMemory
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: ParseTable said: CD_SUCCESS
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: ASIC_INIT Successful
Mar 23 11:49:49 myLenovo kernel[0]: ATY_Init: Card initialize completed

I do not comment out atomSaveRegisters yet, but it's better to remove them since the memory allocated in it is not freed in the project.
LeoMark
Hi, netkas and dong, congratulations for the project! I am a user ati mobility hd2400, I have followed the work of you and I'm anxious for news, some hope for the LVDS connector? sorry for english, I'm Brazilian biggrin.gif
netkas
latest still panics for me O.o
LeoMark
Hey guys ... do you think the project will not have a solution? the project will continue or is this the end of the ATI Mobility ... sad.gif
dong
QUOTE (netkas @ Mar 28 2009, 05:51 AM) *
latest still panics for me O.o


I don't have a later ATI card to test the code. The _RHDReadMC and _RHDWriteMC are different for my x1400 compared to later cards, maybe panic here?
thanoulas
QUOTE (dong @ Mar 31 2009, 09:18 PM) *
I don't have a later ATI card to test the code. The _RHDReadMC and _RHDWriteMC are different for my x1400 compared to later cards, maybe panic here?


Hi there. This is my first post in these forums and i'm really interested in the development of the ATY_HD Framebuffer. I'm a developer myself, so i could lend a hand if you'd like to.

Anyway, i've tested out the ATY_Init code in my hackbook with a mobility HD3650 (M86) and I can verify that it panicks.
Specifically, kernel panicks in rhd_init.c, function:

CODE
void _RHDRegWrite(RHDPtr rhdPtr, CARD16 offset, CARD32 value) {
     *(volatile CARD32 *)((CARD8 *)(rhdPtr->IObase) + offset)) = value;
}


CARD16 offset was 544c, CARD32 value was 8

_RHDRegRead previously returned 0 for the offset 544c

Hope it helps
super_engine
Hi
I am one of hundrends laptop users with an ATI Mobility card (X1350)
Please can attach a compiled file in this thread. So we, no developers, can test it ???

thank you in advance.
dong
QUOTE (thanoulas @ Apr 5 2009, 10:24 AM) *
Hi there. This is my first post in these forums and i'm really interested in the development of the ATY_HD Framebuffer. I'm a developer myself, so i could lend a hand if you'd like to.

Anyway, i've tested out the ATY_Init code in my hackbook with a mobility HD3650 (M86) and I can verify that it panicks.
Specifically, kernel panicks in rhd_init.c, function:

CODE
void _RHDRegWrite(RHDPtr rhdPtr, CARD16 offset, CARD32 value) {
      *(volatile CARD32 *)((CARD8 *)(rhdPtr->IObase) + offset)) = value;
}


CARD16 offset was 544c, CARD32 value was 8

_RHDRegRead previously returned 0 for the offset 544c

Hope it helps

Thanks for your interest and effort.
A roughly search of 0x544C did not give any results from radeonHD or ati open source. I then looked at the binary code of ATY_MegaLodon. It turns out the register address is visited in the procedures of PCIE_Gen1_Enable and PCIE_Gen2_Enable. Below is the disemblyed code of PCIE_Gen2_Enable:
CODE
OSStatus PCIE_Gen2_Enable(DriverGlobal *aDriverRecPtr) {
    UInt32 value, value1;
    pciepw32(aDriverRecPtr, 0xA2, pciepr32(aDriverRecPtr, 0xA2) & ~(1 << 13));
    value = pciepr32(aDriverRecPtr, 0xA2);
    if (value & (1 << 9))
        pciepw32(aDriverRecPtr, 0xA2, ((((value | (1 << 10)) & ~7) | ((value & 0x70) >> 4)) | (1 << 8)) & ~(1 << 7));
    else
        pciepw32(aDriverRecPtr, 0xA2, value | (1 << 13));
    value = pciepr32(aDriverRecPtr, 0xA4);
    if ((value & (1 << 23)) && (value & (1 << 24))) {
        regw32(aDriverRecPtr, 0x5488, regr32(aDriverRecPtr, 5488) & ~(1 << 25));
        regw32(aDriverRecPtr, 0x548C, regr32(aDriverRecPtr, 548C) & ~(1 << 28));
        regw32(aDriverRecPtr, 0x5404, regr32(aDriverRecPtr, 5404) | (1 << 25));
        regw32(aDriverRecPtr, 0x5484, regr32(aDriverRecPtr, 5484) & ~0xF;
        regw32(aDriverRecPtr, 0x5410, regr32(aDriverRecPtr, 5410) | (1 << 23));
        regw32(aDriverRecPtr, 0x548C, regr32(aDriverRecPtr, 548C) & ~0x1E);
        value = (value | 1) & ~2;
        value1 = pciepr32(aDriverRecPtr, 0xA1);
        if (value1 & (1 << 6)) pciepw32(aDriverRecPtr, 0xA1, value1 & ~0x40);
        pciepw32(aDriverRecPtr, 0xA4, ((((value & ~0x300) | 0x300) & ~0x3C000) & ~0x40) | 0x20);
        regw32(aDriverRecPtr, 0x541C, regr32(aDriverRecPtr, 541C) | (1 << 3));
        regw32(aDriverRecPtr, 0x544C, 8);
        regw32(aDriverRecPtr, 0x4088, (regr32(aDriverRecPtr, 4088) & ~0xF) | 2);
        regw32(aDriverRecPtr, 0x544C, 0);
    }
    return noErr;
}

However, I could not find out why it could cause a kernel panic. Hope someone else could give an answer.
thanoulas
QUOTE (dong @ Apr 6 2009, 01:24 AM) *
Thanks for your interest and effort.
A roughly search of 0x544C did not give any results from radeonHD or ati open source. I then looked at the binary code of ATY_MegaLodon. It turns out the register address is visited in the procedures of PCIE_Gen1_Enable and PCIE_Gen2_Enable. Below is the disemblyed code of PCIE_Gen2_Enable:
CODE
OSStatus PCIE_Gen2_Enable(DriverGlobal *aDriverRecPtr) {
     UInt32 value, value1;
     pciepw32(aDriverRecPtr, 0xA2, pciepr32(aDriverRecPtr, 0xA2) & ~(1 << 13));
     value = pciepr32(aDriverRecPtr, 0xA2);
     if (value & (1 << 9))
         pciepw32(aDriverRecPtr, 0xA2, ((((value | (1 << 10)) & ~7) | ((value & 0x70) >> 4)) | (1 << 8)) & ~(1 << 7));
     else
         pciepw32(aDriverRecPtr, 0xA2, value | (1 << 13));
     value = pciepr32(aDriverRecPtr, 0xA4);
     if ((value & (1 << 23)) && (value & (1 << 24))) {
         regw32(aDriverRecPtr, 0x5488, regr32(aDriverRecPtr, 5488) & ~(1 << 25));
         regw32(aDriverRecPtr, 0x548C, regr32(aDriverRecPtr, 548C) & ~(1 << 28));
         regw32(aDriverRecPtr, 0x5404, regr32(aDriverRecPtr, 5404) | (1 << 25));
         regw32(aDriverRecPtr, 0x5484, regr32(aDriverRecPtr, 5484) & ~0xF;
         regw32(aDriverRecPtr, 0x5410, regr32(aDriverRecPtr, 5410) | (1 << 23));
         regw32(aDriverRecPtr, 0x548C, regr32(aDriverRecPtr, 548C) & ~0x1E);
         value = (value | 1) & ~2;
         value1 = pciepr32(aDriverRecPtr, 0xA1);
         if (value1 & (1 << 6)) pciepw32(aDriverRecPtr, 0xA1, value1 & ~0x40);
         pciepw32(aDriverRecPtr, 0xA4, ((((value & ~0x300) | 0x300) & ~0x3C000) & ~0x40) | 0x20);
         regw32(aDriverRecPtr, 0x541C, regr32(aDriverRecPtr, 541C) | (1 << 3));
         regw32(aDriverRecPtr, 0x544C, 8);
         regw32(aDriverRecPtr, 0x4088, (regr32(aDriverRecPtr, 4088) & ~0xF) | 2);
         regw32(aDriverRecPtr, 0x544C, 0);
     }
     return noErr;
}

However, I could not find out why it could cause a kernel panic. Hope someone else could give an answer.


Well I do have another hackbook that I can use to debug the kext with the kernel debug kit. I'll give it a try and tell you what happens.

Another interesting (maybe?) thing is that I skipped the 544c offset and now it panics at the exact same address that is next in the Megalodon fb, 0x4088.
ridgeline
oh for the love of OSX thank you guys for not giving up on this project
Slice
QUOTE (thanoulas @ Apr 5 2009, 06:24 PM) *
Hi there. This is my first post in these forums and i'm really interested in the development of the ATY_HD Framebuffer. I'm a developer myself, so i could lend a hand if you'd like to.

Anyway, i've tested out the ATY_Init code in my hackbook with a mobility HD3650 (M86) and I can verify that it panicks.
Specifically, kernel panicks in rhd_init.c, function:

CODE
void _RHDRegWrite(RHDPtr rhdPtr, CARD16 offset, CARD32 value) {
     *(volatile CARD32 *)((CARD8 *)(rhdPtr->IObase) + offset)) = value;
}


CARD16 offset was 544c, CARD32 value was 8

_RHDRegRead previously returned 0 for the offset 544c

Hope it helps

Bad offset can't cause kernel panic!
Kernel panic can be caused by bad rhdPtr. Check!
dong
QUOTE (Slice @ Apr 16 2009, 01:25 AM) *
Bad offset can't cause kernel panic!
Kernel panic can be caused by bad rhdPtr. Check!


If it was the case, somehow IOmap was read wrong for those cards. People can try change two lines in ATY_Init.cpp from
CODE
IOmap = device->mapDeviceMemoryWithIndex(RHD_MMIO_BAR);
to
CODE
IOmap = device->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress2);
and from
CODE
FBmap = device->mapDeviceMemoryWithIndex(RHD_FB_BAR);
to
CODE
FBmap = device->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
"mapDeviceMemoryWithRegister" is used by native drivers and I think it should found the correct map.
thanoulas
QUOTE (dong @ Apr 16 2009, 04:41 PM) *
If it was the case, somehow IOmap was read wrong for those cards. People can try change two lines in ATY_Init.cpp from
CODE
IOmap = device->mapDeviceMemoryWithIndex(RHD_MMIO_BAR);
to
CODE
IOmap = device->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress2);
and from
CODE
FBmap = device->mapDeviceMemoryWithIndex(RHD_FB_BAR);
to
CODE
FBmap = device->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0);
"mapDeviceMemoryWithRegister" is used by native drivers and I think it should found the correct map.


I'll check that and report back ASAP
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.