It's a trivial observation that today's high-tech life often draws upon ideas and terms from yesterday's fiction. Welcome to the parallel universe of address spaces. (Eerie music in the background.)
Recently I needed a piece of code to provide bus-to- virtual and virtual-to-bus address conversions for my network device driver.
Here's the problem. DMA-based hardware devices use bus address space for transfers, while software normally operates with virtual memory. Kernel device drivers live in between. Luckily, the bus address is the same as the physical RAM address. (Well, as with all generalizations, this one is not entirely true. But it still holds for the currently supported set of BeOS platforms. BTW, can I get an N64 for testing?)
Here's the plan. Create a locked contiguous area and map it, and then it's only a matter of offsets! I find areas very powerful and flexible. The gain: we don't have to worry about crossing physical page boundaries, because the memory chunk is physically contiguous. The disadvantage: memory cannot be moved or swapped out until it's released. The Be Book describes this feature as "justifiable excess":
It was indeed justifiable for my driver. But recently I got a fortune cookie indicating: "If you don't conserve memory, the eco-police will get you!" Signed: Paranoid Anonymous.
The code snippet snip-snips...umh, follows.
/* * * * * * * * * * * * * * * * * * * * * * * Kernel-mode only * virt2phys(), phys2virt() */ #include <KernelExport.h> #defineRNDUP
(x
,y
) (((x
) + (y
) - 1) & ~((y
) - 1)) #defineFOO_SIZE
0x2000 // 2 phys pages #defineAREA_NAME
"foo_area" area_idfoo_area
; static uchar *phys_start_area
; static uchar *virt_start_area
; /* init_driver() * Gets called when the driver is being loaded */ status_tinit_driver
() { uchar *foo_buf
; // driver init essentials...foo_buf
=get_area
(FOO_SIZE
, &foo_area
); if (!foo_buf
) {kprintf
("init: can't get_area()\n"); returnB_NO_MEMORY
; } return (init_area
(foo_buf
)); } /* uninit_driver() * Gets called when the driver is being unloaded */ status_tuninit_driver
() { // driver uninit essentials... return (delete_area
(foo_area
)); } /* get_area() * Create locked contiguous area */ static uchar *get_area
(size_tsize
, area_id *areap
) { uchar *base
; area_idid
;size
=RNDUP
(size
,B_PAGE_SIZE
);id
=create_area
(AREA_NAME
, &base
,B_ANY_KERNEL_ADDRESS
,size
,B_FULL_LOCK
|B_CONTIGUOUS
,B_READ_AREA
|B_WRITE_AREA
); if (id
<B_NO_ERROR
) {kprintf
("get_area: Can't create area\n"); return (NULL
); }memset
(base
,NULL
,size
); // zero it out *areap
=id
; return (base
); } /* init_area() * Map the area, set global ptrs */ status_tinit_area
(uchar *base
) { physical_entryarea_phys_addr
[2]; // only the start + 0 // Get location & size of phys blocks: use the 1st byte of // the 1st page to find the start of the areaget_memory_map
(base
, // vbuf to translate 1, // size of vbuf &area_phys_addr
[0], // tbl to fill out 1); // #of entries // we're relying on the fact that both virt area // and it's phys map are contiguous as advertised.virt_start_area
=base
;phys_start_area
= (uchar *) (area_phys_addr
[0].address
); returnB_NO_ERROR
; } /* phys2virt() * Find the offset, ret virt addr * or panic */ void *phys2virt
(ulongph_addr
) { ulongoffset
;offset
=ph_addr
- (ulong)phys_start_area
; if ( (ph_addr
< (ulong)phys_start_area
) || (offset
>AREA_SIZE
) ) {kprintf
("Out of bounds: PA=0x%08x\n",ph_addr
);kernel_debugger
("p2v"); } return (virt_start_area
+offset
); } /* virt2phys() * Find the offset, ret phys address * or panic */ ulongvirt2phys
(void *v_addr
) { ulongoffset
;offset
= (ulong)v_addr
- (ulong)virt_start_area
; if ( (v_addr
<virt_start_area
) || (offset
>AREA_SIZE
) ) {kprintf
("Out of bounds: VA=0x%08x\n",v_addr
);kernel_debugger
("v2p"); } return ((ulong)phys_start_area
+offset
); }
As always, the most interesting issues (memory protection, exception handling) are beyond the scope of this short article. See you next time here on the pages of the "Menlo Park Bee."
Recommended literature:
http://www.be.com/documentation/be_book/index.html
http://www.be.com/developers/BeDC/March98/presentations/WritingDeviceDrivers/index.html
Linux Device Drivers, by A. Rubini, O'Reilly
Linux Kernel Internals, by M. Beck et. al., 2nd Ed., Addison-Wesley
In part three of this series, we investigated messaging on the BeOS by adding support for a window registry to our application. This week, we'll take the first step on the road to changing what was originally "Hello World" into a real, live, useful BeOS application, by adding a scrollable text editing view to our windows.
As always, if you haven't read the previous three parts of this series, you might want to take a moment to do so:
Developers' Workshop: BeOS Programming Basics: Part 1
Developers' Workshop: BeOS Programming Basics, Part 2
Developers' Workshop: BeOS Programming Basics: Part 3
You can download the source code for this week's project from the Be FTP site:
ftp://ftp.be.com/pub/samples/intro/obsolete/TextWorld.zip
We begin with the source from Part 3, messageworld.cpp
, and perform a
little clean-up work in preparation for this week. First, we need to
include some additional files, so we can use the BTextView
class for
editing:
#include <TextView.h> #include <ScrollView.h>
Let's also fix the application signature to be unique:
const char *APP_SIGNATURE
= "application/x-vnd.BeTextWorld";
Then let's remove the Options menu and the old HelloView
class, since
they won't be needed anymore. So long, simple demo view! We'll miss you!
Also, we can remove the following constants, which are near the top of
the MessageWorld
source:
constuint32MENU_OPT_HELLO
= 'MOhl'; const char *STRING_HELLO
= "Hello World!"; const char *STRING_GOODBYE
= "Goodbye World!";
The HelloView
class and its member functions can be removed as well.
Don't forget to remove the helloview
variable from the
HelloWindow
class.
From the HelloWindow
constructor, remove the following code, so we don't
keep adding the menu to the menu bar:
menu
= newBMenu
("Options");item
=newBMenuItem
("Say Hello", newBMessage
(MENU_OPT_HELLO
));item
->SetMarked
(true
);menu
->AddItem
(item
);menubar
->AddItem
(menu
);
Also, remove the code that adds the HelloView
to the window:
r
.top
=menubar
->Bounds
().bottom
+1;AddChild
(helloview
= newHelloView
(r
));
And, from HelloWindow
::MessageReceived()
, remove the case handler for
MENU_OPT_HELLO
from the switch
block.
Now let's do a quick find-and-replace operation to change all occurrences of "HelloWindow" to "TextWindow" and all occurrences of "HelloApp" to "TextApp".
Now that we've finished our initial tidying up of the code, let's add the
BTextView
. The BTextView
class is used to present (optionally) editable,
(optionally) formatted text in a view. This is great for creating simple
text editors (StyledEdit, for example, uses a
BTextView
for the editing
area of the window).
First, add to the TextWindow
class a new private member variable:
BTextView
*textview
;
Now, let's look at the revised TextWindow
constructor:
TextWindow
::TextWindow
(BRect
frame
) :BWindow
(frame
, "Untitled ",B_TITLED_WINDOW
,B_NOT_RESIZABLE
|B_NOT_ZOOMABLE
) {BRect
r
;BMenu
*menu
; // Add the menu barr
=Bounds
();menubar
= newBMenuBar
(r
, "menu_bar"); // Add File menu to menu barmenu
= newBMenu
("File");menu
->AddItem
(newBMenuItem
("New", newBMessage
(MENU_FILE_NEW
), 'N'));menu
->AddItem
(newBMenuItem
("Open"B_UTF8_ELLIPSIS
, newBMessage
(MENU_FILE_OPEN
), 'O'));menu
->AddItem
(newBMenuItem
("Close", newBMessage
(MENU_FILE_CLOSE
), 'W'));menu
->AddSeparatorItem
();menu
->AddItem
(newBMenuItem
("Save", newBMessage
(MENU_FILE_SAVE
), 'S'));menu
->AddItem
(newBMenuItem
("Save as"B_UTF8_ELLIPSIS
, newBMessage
(MENU_FILE_SAVEAS
)));menu
->AddSeparatorItem
();menu
->AddItem
(newBMenuItem
("Page Setup"B_UTF8_ELLIPSIS
, newBMessage
(MENU_FILE_PAGESETUP
)));menu
->AddItem
(newBMenuItem
("Print"B_UTF8_ELLIPSIS
, newBMessage
(MENU_FILE_PRINT
), 'P'));menu
->AddSeparatorItem
();menu
->AddItem
(newBMenuItem
("Quit", newBMessage
(MENU_FILE_QUIT
), 'Q'));menubar
->AddItem
(menu
);AddChild
(menubar
); // Add the text viewBRect
textframe
=Bounds
();textframe
.top
=menubar
->Bounds
().bottom
+ 1.0;BRect
textrect
=textframe
;textrect
.OffsetTo
(B_ORIGIN
);r
.InsetBy
(3.0,3.0);AddChild
(textview
= newBTextView
(textframe
, "text_view",textrect
,B_FOLLOW_ALL_SIDES
,B_WILL_DRAW
|B_PULSE_NEEDED
)); // Tell the application that there's one more window // and get the number for this untitled window.Register
(true
);Show
(); }
There are two changes here worth noting. First, the AddChild()
call to
add the menu bar to the window now occurs only after all the menus are
added. This is critical (and fixes a minor bug in the existing code):
although the BMenuBar
class adjusts the height of the menu bar given the
height of the text it contains, if it doesn't have any text in it when we
add it to the window, it won't calculate the height. So it was assuming
that the height of the menu bar is zero (which is true given what it
knows about the menu bar at the time).
By including the
after the menus are all in place, the
height of the menu bar's frame rectangle is correctly adjusted. This is
necessary because the code that computes the frame rectangle for the
AddChild
(menubar
)BTextView
relies on the menu bar's frame rectangle (so that our code will
work regardless of the user's settings in the Fonts preferences
application).
The other change is that instead of adding a HelloView
to the code, we
now add a BTextView
. Its frame rectangle is computed by taking the bounds
of the entire window and moving the top edge down below the bottom of the
menu bar.
A BTextView
actually requires two rectangles as input. The first
represents the actual frame rectangle of the text view itself. The other,
the text rectangle, represents the area inside the view that the text is
actually drawn in. This lets you establish borders around the edges of
the text, and determines how wide the lines of the text are. The bottom
edge of this rectangle is ignored, and will change as the text gets
longer or shorter while being edited.
The text rectangle is simply the frame rectangle, inset by three pixels
on every side. We use
to offset the text rectangle so
its top-left corner is at (0,0)—the text rectangle is local to the
text view, so we need to make this adjustment.
OffsetTo
(B_ORIGIN
)
Finally, we instantiate the BTextView
object and add it to the window.
If you compile and run the application now, you have a very simple "notepad-style" text editor, where you can create multiple windows using the New option in the
menu, and enter text in them all.But there are some obvious shortcomings. Let's go through them, one by one, and correct them.
First, you have to click in the BTextView
before you can start editing.
This is easy to fix. The problem is that, by default, the text view
doesn't have the focus. "Focus" is a fancy way of saying "gets dibs on
keys the user presses." So let's give the BTextView
the default focus.
Add the following line of code, right after the AddChild()
call that adds
the BTextView
to the window:
textview
->MakeFocus
(true
);
This establishes that keystrokes should, by default, be sent to the
BTextView
. Try compiling and running the application again to see that
this is, in fact, the case.
The next shortcoming is that the window is a fixed size. This is pretty
dull. Let's go ahead and make the window resizable. As you see, our
current TextWindow
constructor begins like this:
TextWindow
::TextWindow
(BRect
frame
) :BWindow
(frame
, "Untitled ",B_TITLED_WINDOW
,B_NOT_RESIZABLE
|B_NOT_ZOOMABLE
) {
Just change this to:
TextWindow
::TextWindow
(BRect
frame
) :BWindow
(frame
, "Untitled ",B_DOCUMENT_WINDOW
, 0) {
Now run this. Well, the window's resizable, but the resize box is leaving droppings behind when you use it, and it'd be really nice if there were a scroll bar in there.
So now we introduce the BScrollView
class. This is a type of view that
contains either a horizontal or vertical scroll bar (or both), as well as
any other view. The scroll bars then let the user scroll the child view.
In our case, we'll create a BScrollView
and add
the BTextView
to it, so
the user can then use scroll bars to scroll through the BTextView
. This
changes the code that creates and adds the BTextView
(and BScrollView
) to look like this:
BRect
textframe
=Bounds
();textframe
.top
=menubar
->Bounds
().bottom
+ 1.0;textframe
.right
-=B_V_SCROLL_BAR_WIDTH
;BRect
textrect
=textframe
;textrect
.OffsetTo
(B_ORIGIN
);r
.InsetBy
(3.0,3.0);textview
= newBTextView
(textframe
, "text_view",textrect
,B_FOLLOW_ALL_SIDES
,B_WILL_DRAW
|B_PULSE_NEEDED
);AddChild
(scrollview
= newBScrollView
("scroll_view",textview
,B_FOLLOW_ALL_SIDES
, 0,false
,true
,B_NO_BORDER
));textview
->MakeFocus
(true
);
Note that we're subtracting B_V_SCROLL_BAR_WIDTH
from the right edge of
the BTextView
's frame rectangle. This makes room for the vertical scroll
bar.
The BTextView
is now allocated and saved, without actually adding it to
the window.
Then the BScrollView
is created, with the name "scroll_view." Note the
reference to textview; the BScrollView
constructor automatically makes
the specified view (our BTextView
in this case) the target of the scroll
bars in the BScrollView
.
We specify that the view should have no horizontal scroll bar (false
),
but should have a vertical scroll bar (true
). Finally, we indicate that
we don't need a border, by specifying the B_NO_BORDER
value. Be sure to
read over the BScrollView
section in the Be Book for a more in-depth look
at exactly what's happening here.
Note also that we've added:
BScrollView
*scrollview
;
To the TextWindow
class's private members.
If you now compile and run this, you should have a very familiar-looking
editing window—completely resizable and scrollable. But you'll notice
that word wrapping is odd, because the width of the text rectangle isn't
being changed as you make the window wider and narrower. Let's fix that
by adding a FrameResized()
function to our window:
Add the following line to the TextWindow
's public member functions:
virtual voidFrameResized
(floatwidth
, floatheight
);
Then add the code for TextWindow::FrameResized()
:
voidTextWindow
::FrameResized
(floatwidth
, floatheight
) {BRect
textrect
=textview
->TextRect
();textrect
.right
=textrect
.left
+ (width
-B_V_SCROLL_BAR_WIDTH
- 3.0);textview
->SetTextRect
(textrect
); }
This code gets the current text rectangle from the BTextView
, adjusts the
right edge, and updates the text rectangle by calling SetTextRect()
.
The right edge is corrected by taking the left edge, adding the window's
new width, and subtracting the width of the scroll bar and the number
3.0, which, as you recall, is the amount by which we inset the text
rectangle from the BTextView
's frame rectangle.
If you compile and run this application, you now have an editor which always keeps the text wrapped inside the edit window.
That's all for this time. There are obviously many things we can do to
make this application more useful, and you can probably figure most of
them out yourself by browsing through the BTextView
and BScrollView
sections of the Be Book.
Next time (in about six weeks), we'll add an Edit menu to add the standard editing features, and we'll add code to open existing text files, and save documents to disk. Until then, play with this code and see what you can do with it.
It is tempting to compare our PC Expo experience to our participation in last fall's Comdex. In Las Vegas, graciously hosted by our long-time partners Umax, we showed the released PowerPC version of the BeOS and a pre-release Intel implementation.
Most visitors there first gave us quizzical looks: Who are you, what are you doing here? Things improved after the demonstration, but we didn't have a product for this mostly Wintel crowd. Our positioning was unclear and we couldn't show many applications.
At PC Expo, we had our own booth. We had an Intel product, complete with a fresh 3.1 upgrade. We stated and demonstrated our position as a specialized OS for A/V applications, coexisting with Windows, the general purpose OS. We also had Be Developers showing and selling applications.
One rewarding moment that repeated itself over and over came at the conclusion of our demos—a wave of people walking to the Be Developer stations to look at their applications.
Another interesting pattern was the reaction of visitors whose badges identified them as IS managers. We thought they couldn't possibly be interested in anything but Microsoft platforms. Many reminded us, however, that they had a life besides their day job and they were interested in our media capabilities. Some even ventured the opinion that they saw possible uses for our technology in their companies if or when we proved capable and solid enough.
Overall, we enjoyed good traffic in spite of being in the "other" section. Unlike at Comdex, most visitors had already heard about us and they seemed pleased with the time they spent on our booth.
At one point, I spotted Michael Dell walking by—taking time out from the executive suite—and we chatted for a few moments. He amiably expressed an interest in trying the BeOS on one of his machines, and we'll do our best to give him a first good impression.
I won't belabor the point, but it would be nice, when you order your Dell System on their Web site, to have—alongside your choice of modems and applications software—a button to click for a factory install of the BeOS and choice of applications as an option.
This said, we have much work ahead of us, focusing on one goal: to bring customers to our developers. From improvements to our product to better e-commerce capabilities, evangelism, marketing, PR, OEM agreements, to cite but a few building blocks, with BeOS applications now emerging, the next steps become pretty obvious.
Our gratitude goes to all the people inside and outside Be who made this first PC Expo a success. I'll take the risk of singling out Sylvie Pelaprat for masterminding the operation, not least of all because she'd have been criticized if things had gone awry.
In an earlier column I mentioned NYC cab drivers and Parisian maître d's. I just sampled the former, and found them reasonably friendly and efficient. Now I'll go and refresh my memories of the latter.