Есть некая полусамопальная PCI-железка, занимается формированием изображения на монохромной светодиодной матрице (думаю все видели такие в виде "бегущей строки"). У этой железки есть отображаемая в адресное пространство процессора память, содержащая битовую карту изображения. Мне нужно в эту память писать для формирования картинки. Сам процесс записи в общем ничего интересного не представляет, но вот куда писать -- непонятно. Все под DOS. Для начала я перебираю все шины в поиках этой железки (использую int 1Ah), а потом извлекаю ее свойства. Вот тут у меня три непонятки: 1. От кого зависит номер BAR, в котором лежит нужный мне адрес? Может ли быть так, что на одной машине он один, а на другой будет уже другим? 2. Как мне узнать размер отображаемого куска? Железки бывают разных модификаций с разными объемами памяти, а мне нужно стобы все было универсально. Ведь в Windows в свойствах любой железки можно увидеть полные диапазоны памяти, значит это возможно. А в BAR-ах есть только начало. 3. Глазами нужный BAR я вижу и при попытке его использования все работает, но на некоторых системах вместо реального F0000000h (например) там почему-то лежит что-то вроде F0000008h. Причем это "8" явно лишнее, реально память начитается по первому адресу, откуда оно берется и почему оно на разных машинах разное? Спасибо.
Обычно BAR адресс вещь не фиксированная. Установкой BAR регистров занимается BIOS. Bass address registers - BAR Bass address (базовый адрес)-это адрес регистров устройства. Обычно эти регистры отображаются на физические адреса памяти. Или более устаревшая модель в адресатное пространство портов ввода/вывода. Это и есть основные регистры PCI устройства. Через них происходит программирование, но информация эта зависит от производителя. Более детально о Bass address. Если все биты 32 установлены в 0, то Bass address не записан в ячейку. Если бит 0 установлен в 1, то Bass address задает базовый адрес в пространстве портов I/O. Иначе если бит 0 установлен в 0, то Bass address задает базовый адрес в пространстве адресов памяти. Если наш Bass address задает базовый адрес в пространстве портов I/O. То он имеет следующий формат. Код (Text): +------------------------------------------------+-+-+ |31 2|1|0| +------------------------------------------------+-+-+ | Bass address |0|1| +------------------------------------------------+-+-+ 32 бита. Нулевой бит указывает на то, что это пространство портов I/O. Первый бит зарезервирован. Остальные указываюсь адрес базового порта ввода вывода. Если наш Bass address задает базовый адрес в пространстве памяти. То он имеет следующий формат. Код (Text): +--------------------------------------------+-+-+-+-+ | 4|3|2|1|0| +--------------------------------------------+-+-+-+-+ | Bass address | | |0| +--------------------------------------------+-+-+-+-+ Либо 32 бита, либо 64 бита. Нулевой бит указывает на то, что это пространство памяти. Следующие два бита 1 и 2 определяют тип записи. 00- 32 битная запись любой адрес 01- 32 битная запись, но адрес меньше мегабайта. 02- 64 битная запись любой адрес 03- зарезервировано Бит 3 – Prefetchable – предпочтения выставляется в один если не вызывает побочных явления при чтении. Если у нас отображаются регистры в память. Так называемые Memory Mapped I/O то при чтении могут срабатывать триггеры, которые могут влиять на работу устройства. Поэтому для регистров этот бит всегда опущен (not prefetchable). А вот для линейного буфера видео карты этот бит выставлен (Prefetchable). Остальные биты базовый адрес. Если вы хотите узнать размер памяти, которая отображается в адресное пространство. Или число портов ввода вывода. То вы должны выставить максимальное значение FFFFFFFF и прочитав его. Устройство обнулит биты, которые соответствуют его диапазону адресов. Замечу, что выставляемый адрес должен быть кратным размеру его диапазона, выровнен по этому размеру, поэтому и происходит обнуление битов. Всякое PCI устройство может иметь свой встроенный BIOS. Которая располагается по Expansion ROM Base Address. Стандартный диапазон для всех БИОСов PCI устройств у PC-AT совместных компьютеров 0C0000h-0DFFFFh. Expansion ROM Base Address – Имеет следующий формат Код (Text): +--------------------------------+-----------------+-+ |31 11|10 1|0| +--------------------------------+-----------------+-+ |Expansion ROMBase Address | резерв |1| +--------------------------------+-----------------+-+ Нулевой бит Expansion ROM Enable это бит, который отвечает - будет ли задействован БИОС или нет. Expansion ROMBase Address – старшие 21 бит адреса БИОСа. Если вы попробуете записать значение 0FFFFFFFEh тогда мы получим в ответ размер БИОСа. После нужно восстановить адрес, предварительно схоронив его. Если вернулись все 0, то БИОСа не существует. Если смотреть от младших битов к старшим, то там идут нули первый единичный бит сигнализирует размер БИОСа. Пример |11111111 11111111 00000|000 0000000|X| Размер 64Кб его проще всего вычислить по формуле Size:=-Addr; Или команда ассемблера NEG Размер БИОСа может колебаться в приделах от 64КБ до 16МБ. А его адрес должен быть кратен размеру БИОСа. Для видео карты БИОС должен располагаться по адресу 0C0000h поэтому в Expansion ROMBase Address у нее может быть 0. Это все и многое другое описанно в спецификации PCI. Даже есть руский перевод на версию 2.0 PCI_Rev_20_rus
Pavia А если устройство имеет несколько диапазонов отображаемой памяти, то можно как-то узнать какой кусок куда попал или только по предполагаемому размеру можно предположить? Или не бывает таких устройств, чтобы отображали несколько участков с одинаковым набором флагов в младших битах? Спасибо за наводку, в общих чертах понял, буду пробовать.
В спецификации железки точно прописанно, какой BAR за что отвечает. А адресс узнается путем чтения его содержимого, у каждого BAR он свой. А что за железка узнаем по Vendor_ID, Device_ID Sub_Device_ID и тд... Бывают. Отчего же не быть?
Pavia То есть не зависимо от системы у конкретной железки каждый BAR всегда будет иметь один и тот же смысл? Документации на железку нет, приходится методом тыка все определять.
Pavia Это понятно, спасибо. Но обнаружилась новая проблема: железка возвращает сильно завышенный объем памяти. Если реально на ней распаяно два мегабайта, то почему после записи FFFFFFFF она возвращает FFC00000? Это уже 4 мегабайта. Причем при записи выше законных двух метров начитаются сильные задержки, а чтение из этих адресов всегда возвращает одно и то же значение (в зависимости от системы это либо 00 либо FF), т.е. никаких четырех мегабайт там точно нет и не было. На железку пинать не могу, потому что на проверку этим грешат например и видеокарты. Вот собрал по имеющимся статистику (первая пара чисел -- реальный диапазон видеопамяти, третье число -- результат чтения и инверсии соответствующего BAR после записи туда FFFFFFFF): D0000000..D7FFFFFF, DFFFFFFF (GF7300GS/128M) E0000000..E0FFFFFF, E1FFFFFF (VANTA/16M) E6000000..E6FFFFFF, E7FFFFFF (TNT/16M) E6000000..E67FFFFF, E6FFFFFF (TNT/8M) E6000000..E63FFFFF, E67FFFFF (SIS/4M) F0000000..F0FFFFFF, F7FFFFFF (VMWare/16M) Причем если присмотреться, то можно увидеть что во всех кроме последнего (весьма специфического) случаях объем завышен ровно в два раза, т.е. выставлен один лишний бит. Как же узнать реальный размер памяти?
misha_irpen Скорее всего ошибка в коде. У меня таких проблем не было. Темболее BAR регистров от 1 до 6 и в видео картах один используется под память другой под регистры отоброженные в память.
Pavia Вот мой код: Код (Text): function ReadConfigDWord(const Bus, Device, Func: Byte; const Reg: Word): DWord; var Regs: TRealRegs; begin Regs.AX:= $B10A; Regs.BL:= (Func and $07) or (Device shl 3); Regs.BH:= Bus; Regs.DI:= Reg; RealIntr($1A, Regs); if Regs.AH = $00 then Result:= DWord(Regs.ECX) else Result:= $FFFFFFFF; end; function WriteConfigDWord(const Bus, Device, Func: Byte; const Reg: Word; const Data: DWord): Boolean; var Regs: TRealRegs; begin Regs.AX:= $B10D; Regs.BL:= (Func and $07) or (Device shl 3); Regs.BH:= Bus; Regs.DI:= Reg; Regs.ECX:= Integer(Data); RealIntr($1A, Regs); Result:= (Regs.AH = $00); end; function GetAddressRange(const Bus, Device, Func: Byte; const Reg: Word; out Address, Range: DWord): Boolean; var Org: DWord; begin Org:= ReadConfigDWord(Bus, Device, Func, Reg); if (Org <> 0) and (Org <> $FFFFFFFF) and (Org and $00000001 = 0) and WriteConfigDWord(Bus, Device, Func, Reg, $FFFFFFFF) then begin Address:= Org and $FFFFFFF0; Range:= not (ReadConfigDWord(Bus, Device, Func, Reg) and $FFFFFFF0); WriteConfigDWord(Bus, Device, Func, Reg, Org); Result:= True; end else Result:= False; end; Я обшариваю все шины и когда нахожу VendorID <> FFFF, натравливаю на найденное устройство GetAddressRange (при этом Func = 0; Reg = [10h, 14h, 18h, 1Ch, 20h, 24h]), а потом вывожу на экран полученные Address и Range.
misha_irpen Да все верно. Значит раньше этого не замечал. У видюшак есть еще одна особенность это GART возможно в этом дело. Тогда надо смотреть может где еще зашит реальный размер. Так, как у тебя самодельная железка то новерно можно попробовать привезаться к Subsystem_ID, Revision_ID. Надо будет составить базу.