Serial Communication seems to mess up VGA in QEMU

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
dengeltheyounger
Member
Member
Posts: 25
Joined: Wed Feb 24, 2021 8:52 pm

Serial Communication seems to mess up VGA in QEMU

Post by dengeltheyounger »

So, I'm in the process of working on page frame allocation. I decided to set up serial communication in order to make it a little bit easier to debug (right now, there's no file IO, and I don't have keyboard input set up, which makes having an internal kernel buffer difficult to work with). Using qemu, I'm doing the option -serial file:serial.log, which seems to work well. The only problem is that the whole screen is being filled with weird characters (Ideally, I'd like to also print to console for some cases, and then to have qemu write to file for other cases). I'm not sure if that's normal for serial communication.

This is what the screen looks:
debug.png
This is the code to set up uart:

Code: Select all

#include <i386/uart.h>

int init_com_port(uint16_t port) {
	// Disable interrupts
	outb(port + 1, 0x00);
	// enable DLAB
	outb(port + 3, 0x80);
	// Set divisor to 3, 38400 buad
	outb(port + 0, 0x03);
	// hi byte
	outb(port + 1, 0x00);
	// 8 bits, no parity, one stop bit
	outb(port + 3, 0x03);
	// enable FIFO, clear them, with 14-byte threshold
	outb(port + 2, 0xC7);
	// IRQs enabled RTS/DSR set
	outb(port + 4, 0x0B);
	// Set in loopback mode, test serial chhip
	outb(port + 4, 0x1E);
	// Test serial chip
	outb(port + 0, 0xAE);

	// Check if serial is faulty
	if (inb(port + 0) != 0xAE)
		return 0;

	// if serial is not faulty set it in normal operation mode
	outb(port + 4, 0x0F);
	return 1;
}

int is_transmit_empty(uint16_t port) {
	return inb(port + 5) & 0x20;
}

void serial_putchar(uint16_t port, char a) {
	while (!is_transmit_empty(port));

	outb(port, a);
}

void serial_writestring(uint16_t port, const char *string) {

	serial_write(port, string, strlen(string));
}

void serial_write(uint16_t port, const char *string, size_t n) {
	for (size_t i = 0; i < n; ++i) {
		serial_putchar(port, string[i]);
	}
}
Currently, it is set up with interrupts disabled. I'm not sure if that explains why the screen ends up looking like that. The com port that is written to is always COM1.

This is the code for the memory map initializer:

Code: Select all

int init_memory_map(multiboot_info_t *mbd) {

	/* We only actually use the mmap_t here, and so we're going
	 * to go ahead and define it within the function
	 */
	struct mmap_t {
		uint32_t size;
		uint64_t base;
		uint64_t len;
		uint32_t type;
	} __attribute((packed));

	unsigned char check_flag = (1 << 0) & mbd->flags;
	
	// Check flags to make sure memory mapping is sane
	if (!check_flag) {
		printf("Flag is not set at bit 0!\n");
		return 0;
	}

	check_flag = (1 << 6) & mbd->flags;

	if (!check_flag) {
		printf("Flag is not set at bit 6!\n");
		return 0;
	}

	// Get the address and length to set up
	struct mmap_t *mmap_addr = (struct mmap_t *) mbd->mmap_addr;
	uint64_t mmap_length = (uint64_t) mbd->mmap_length;

	// Get the number of entries
	uint32_t arr_size = mmap_length / sizeof(struct mmap_t); 
	

	// TODO: Remove this and just use mbd->mmap_addr
	struct mmap_t entries[arr_size];
	// Copy the multiboot info into entries
	memcpy(entries, mmap_addr, mmap_length);

	// TODO: Remove this code later
	for (uint32_t i = 0; i < arr_size; ++i) {
		printf_serial("Size: %u\n", entries[i].size);
		printf_serial("Base: 0x%llx\n", entries[i].base);
		printf_serial("Size: %llu\n", entries[i].len);
		printf_serial("Type: 0x%lx\n\n", entries[i].type);
	}	

	/* Get the base + length to determine total "usable"
	 * ram
	 */
	uint64_t last_address = entries[arr_size-1].base +
	       			entries[arr_size-1].len;

	printf_serial("Last address: 0x%llx\n", last_address);

	// Get the number of pages
	uint32_t blocks = last_address / 4096; 

	printf_serial("Number of blocks: %u\n", blocks);

	// Get the number of bytes for the bitmap, each bit is 1 page
	bitmap_size = blocks / 8;

	printf_serial("Bitmap size: %u\n", bitmap_size);

	uint8_t bitmap[bitmap_size];

	printf_serial("Preparing to memset bitmap...\n");
	// Set all bits to 1
	memset(bitmap, 0xFF, bitmap_size);
        // It never gets passed this point
	printf_serial("Finished memsetting bitmap...\n");
printf_serial, is literally the same as my printf implementation, except it calls the serial_write type functions in instead of terminal_write. It specifically writes to COM1 (I'm not really at the point of writing a generalized implementation that works out the addresses of all of the COM ports and allows them all to be set up in their unique ways).

As best I can tell, it is either a bug with the serial console, or it is a bug with the physical memory initializer. Any idea what's going on?
Octocontrabass
Member
Member
Posts: 5567
Joined: Mon Mar 25, 2013 7:01 pm

Re: Serial Communication seems to mess up VGA in QEMU

Post by Octocontrabass »

dengeltheyounger wrote:I'm not sure if that's normal for serial communication.
No, of course it's not normal. There is a bug in your code.
dengeltheyounger wrote:Any idea what's going on?
The only thing that stands out to me is that your code will use a lot of stack space, but it's hard to tell if that's the problem without running it under a debugger. Speaking of which, you can use a debugger to catch whatever is writing to the screen.
dengeltheyounger
Member
Member
Posts: 25
Joined: Wed Feb 24, 2021 8:52 pm

Re: Serial Communication seems to mess up VGA in QEMU

Post by dengeltheyounger »

Octocontrabass wrote:
dengeltheyounger wrote:I'm not sure if that's normal for serial communication.
No, of course it's not normal. There is a bug in your code.
dengeltheyounger wrote:Any idea what's going on?
The only thing that stands out to me is that your code will use a lot of stack space, but it's hard to tell if that's the problem without running it under a debugger. Speaking of which, you can use a debugger to catch whatever is writing to the screen.
So, I've noticed two different things. The first is that the program prints to console normally if I return immediately before the memset. For that reason, it looks like the memset has caused the write to go into the VGA buffer. The other thing is that I've attached gdb to the kernel. I went ahead and ran
info locals
and this was the result:
check_flag = <optimized out>
mmap_addr = <optimized out>
mmap_length = <optimized out>
arr_size = <optimized out>
entries = {}
last_address = <optimized out>
blocks = 1048576
bitmap = {<error reading variable>
ndx = <optimized out>
base = <optimized out>
limit = <optimized out>
address = <optimized out>
to_allocate = <optimized out>
bad_alloc = 0
new_location = <optimized out>
When I attempted to get the first element of bitmap
print bitmap[0]
I got this:
value requires 131072 bytes, which is more than max-value-size
I also discovered that the kernel jumped into the "bound range exceeded" exception. It's looking like the issue (as you mentioned) is that my code is requiring too much stack space, and for that reason is crashing. Because the stack has exploded, the pusha instruction doesn't seem to be executing properly, which is why my exception handler never told me what was going on (at least, that's my reasoning).

It looks like this mystery has been solved. The bitmap was designed to have each bit represent a block of 4096 bytes. Taking the very top address, dividing by 4096, and then dividing by 8 seems to still yield about 130 K. I wanted it set up so that the entire bitmap would be stored locally, and then have my bitmap pointer to point to the locally stored bitmap. The page frame allocator would then use that locally stored bitmap (through the global pointer) in order to find some space in ram to permanently store the bitmap, and then the initializer would reset the global pointer to whatever the page frame allocator returned (solving the allocating memory for the allocator without an allocator problem). However, I'll need to go back to the drawing board and a better way to handle the initialization step for the physical memory manager.

Hopefully this will serve as a lesson to other newbies like me about the need for good data structures and good algorithms when designing a kernel.
Post Reply