pull/268/merge
Hennik Hunsaker 8 months ago committed by GitHub
commit 5f5b7c925d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -5,3 +5,4 @@
*.elf
*.sym
.DS_STORE
os-tutorial.pdf

@ -1,3 +1,6 @@
Environment
===========
*Concepts you may want to Google beforehand: linux, mac, terminal, compiler, emulator, nasm, qemu*
**Goal: Install the software required to run this tutorial**

@ -1,3 +1,6 @@
Bootsector: Barebones
=====================
*Concepts you may want to Google beforehand: assembler, BIOS*
**Goal: Create a file which the BIOS interprets as a bootable disk**
@ -25,7 +28,7 @@ e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00
```
It is basically all zeros, ending with the 16-bit value
`0xAA55` (beware of endianness, x86 is little-endian).
`0xAA55` (beware of endianness, x86 is little-endian).
The first three bytes perform an infinite jump
Simplest boot sector ever
@ -38,12 +41,12 @@ simple assembler code:
```nasm
; Infinite loop (e9 fd ff)
loop:
jmp loop
jmp loop
; Fill with 510 zeros minus the size of the previous code
times 510-($-$$) db 0
; Magic number
dw 0xaa55
dw 0xaa55
```
To compile:

@ -1,3 +1,6 @@
Bootsector: Print
=================
*Concepts you may want to Google beforehand: interrupts, CPU
registers*
@ -14,7 +17,7 @@ is a general interrupt for video services.
`0x0e` on `ah` tells the video interrupt that the actual function
we want to run is to 'write the contents of `al` in tty mode'.
We will set tty mode only once though in the real world we
We will set tty mode only once though in the real world we
cannot be sure that the contents of `ah` are constant. Some other
process may run on the CPU while we are sleeping, not clean
up properly and leave garbage data on `ah`.
@ -39,7 +42,7 @@ jmp $ ; jump to current address = infinite loop
; padding and magic number
times 510 - ($-$$) db 0
dw 0xaa55
dw 0xaa55
```
You can examine the binary data with `xxd file.bin`

@ -1,3 +1,6 @@
Bootsector: Memory
==================
*Concepts you may want to Google beforehand: memory offsets, pointers*
**Goal: Learn how the computer memory is organized**

@ -1,3 +1,6 @@
Bootsector: Stack
=================
*Concepts you may want to Google beforehand: stack*
**Goal: Learn how to use the stack**
@ -11,5 +14,5 @@ decremented)
This lesson is quite straightforward, so jump ahead to the code.
I suggest that you try accessing in-stack memory addresses by yourself,
I suggest that you try accessing in-stack memory addresses by yourself,
at different points in the code, and see what happens.

@ -1,3 +1,6 @@
Bootsector: Functions and Strings
=================================
*Concepts you may want to Google beforehand: control structures,
function calling, strings*
@ -52,7 +55,7 @@ endif:
Think in your head in high level, then convert it to assembler in this fashion.
There are many `jmp` conditions: if equal, if less than, etc. They are pretty
There are many `jmp` conditions: if equal, if less than, etc. They are pretty
intuitive but you can always Google them
@ -119,7 +122,7 @@ to make sure that we are reading the correct data. File `boot_sect_print_hex.asm
extends `boot_sect_print.asm` to print hex bytes, not just ASCII chars.
Code!
Code!
-----
Let's jump to the code. File `boot_sect_print.asm` is the subroutine which will

@ -1,3 +1,6 @@
Bootsector: Segmentation
========================
*Concepts you may want to Google beforehand: segmentation*
**Goal: learn how to address memory with 16-bit real mode segmentation**

@ -1,4 +1,7 @@
*Concepts you may want to Google beforehand: hard disk, cylinder, head, sector,
Bootsector: Disk
================
*Concepts you may want to Google beforehand: hard disk, cylinder, head, sector,
carry bit*
**Goal: Let the bootsector load data from disk in order to boot the kernel**
@ -57,7 +60,7 @@ There are two quick options:
1. Try the flag `-fda` for example, `qemu -fda boot_sect_main.bin` which will set `dl`
as `0x00`, it seems to work fine then.
2. Explicitly use the flag `-boot`, e.g. `qemu boot_sect_main.bin -boot c` which
1. Explicitly use the flag `-boot`, e.g. `qemu boot_sect_main.bin -boot c` which
automatically sets `dl` as `0x80` and lets the bootloader read data

@ -1,9 +1,12 @@
*Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video
32-bit: Print
=============
*Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video
memory*
**Goal: Print on the screen when on 32-bit protected mode**
32-bit mode allows us to use 32 bit registers and memory addressing,
32-bit mode allows us to use 32 bit registers and memory addressing,
protected memory, virtual memory and other advantages, but we will lose
BIOS interrupts and we'll need to code the GDT (more on this later)
@ -18,7 +21,7 @@ The formula for accessing a specific character on the 80x25 grid is:
`0xb8000 + 2 * (row * 80 + col)`
That is, every character uses 2 bytes (one for the ASCII, another for
That is, every character uses 2 bytes (one for the ASCII, another for
color and such), and we see that the structure of the memory concatenates
rows.

@ -1,3 +1,6 @@
32-bit: GDT
===========
*Concepts you may want to Google beforehand: GDT*
**Goal: program the GDT**
@ -9,7 +12,7 @@ In 32-bit mode, segmentation works differently. Now, the offset becomes an
index to a segment descriptor (SD) in the GDT. This descriptor defines
the base address (32 bits), the size (20 bits) and some flags, like
readonly, permissions, etc. To add confusion, the data structures are split,
so open the os-dev.pdf file and check out the figure on page 34 or the
so open the os-dev.pdf file and check out the figure on page 34 or the
Wikipedia page for the GDT.
The easiest way to program the GDT is to define two segments, one for code

@ -1,3 +1,6 @@
32-bit: Enter
=============
*Concepts you may want to Google beforehand: interrupts, pipelining*
**Goal: Enter 32-bit protected mode and test our code from previous lessons**
@ -17,7 +20,7 @@ and take a look at the code.
After entering 32-bit mode, we will call `BEGIN_PM` which is the entry point
for our actual useful code (e.g. kernel code, etc). You can read the code
at `32bit-main.asm`. Compile and run this last file and you will see the two
at `32bit-main.asm`. Compile and run this last file and you will see the two
messages on the screen.
Congratulations! Our next step will be to write a simple kernel

@ -1,3 +1,6 @@
Kernel: Crosscompiler
=====================
*Concepts you may want to Google beforehand: cross-compiler*
**Goal: Create a development environment to build your kernel**
@ -6,7 +9,7 @@ If you're using a Mac, you will need to do this process right away. Otherwise, i
for a few more lessons. Anyway, you will need a cross-compiler once we jump to developing in a higher
language, that is, C. [Read why](http://wiki.osdev.org/Why_do_I_need_a_Cross_Compiler%3F)
I'll be adapting the instructions [at the OSDev wiki](http://wiki.osdev.org/GCC_Cross-Compiler).
I'll be adapting the instructions [at the OSDev wiki](http://wiki.osdev.org/GCC_Cross-Compiler).
Required packages
@ -63,10 +66,10 @@ tar xf gcc-4.9.1.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-4.9.1/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc
```
That's it! You should have all the GNU binutils and the compiler at `/usr/local/i386elfgcc/bin`, prefixed by `i386-elf-` to avoid

@ -1,3 +1,6 @@
Kernel: C
=========
*Concepts you may want to Google beforehand: C, object code, linker, disassemble*
**Goal: Learn to write the same low-level code as we did with assembler, but in C**

@ -1,3 +1,6 @@
Kernel: Barebones
=================
*Concepts you may want to Google beforehand: kernel, ELF format, makefile*
**Goal: Create a simple kernel and a bootsector capable of booting it**

@ -1,3 +1,6 @@
Checkpoint
==========
*Concepts you may want to Google beforehand: monolithic kernel, microkernel, debugger, gdb*
**Goal: Pause and organize our code a little bit. Then learn how to debug the kernel with gdb**

@ -1,3 +1,6 @@
Video: Ports
============
*Concepts you may want to Google beforehand: I/O ports*
**Goal: Learn how to use the VGA card data ports**

@ -1,3 +1,6 @@
Video: Driver
=============
*Concepts you may want to Google beforehand: VGA character cells, screen offset*
**Goal: Write strings on the screen**
@ -44,7 +47,7 @@ Like `kprint_at`, `print_char` allows cols/rows to be `-1`. In that case it retr
the cursor position from the hardware, using the `ports.c` routines.
`print_char` also handles newlines. In that case, we will position the cursor offset
to column 0 of the next row.
to column 0 of the next row.
Remember that the VGA cells take two bytes, one for the character itself and another one
for the attribute.

@ -1,3 +1,6 @@
Video: Scroll
=============
*Concepts you may want to Google beforehand: scroll*
**Goal: Scroll the screen when the text reaches the bottom**

@ -1,3 +1,6 @@
Interrupts
==========
*Concepts you may want to Google beforehand: C types and structs, include guards, type attributes: packed, extern, volatile, exceptions*
**Goal: Set up the Interrupt Descriptor Table to handle CPU interrupts**
@ -24,7 +27,7 @@ From now on, our C header files will also have include guards.
Interrupts
----------
Interrupts are one of the main things that a kernel needs to
Interrupts are one of the main things that a kernel needs to
handle. We will implement it now, as soon as possible, to be able
to receive keyboard input in future lessons.
@ -37,13 +40,13 @@ programming the IDT in assembly, we'll do it in C.
`cpu/idt.h` defines how an idt entry is stored `idt_gate` (there need to be
256 of them, even if null, or the CPU may panic) and the actual
idt structure that the BIOS will load, `idt_register` which is
idt structure that the BIOS will load, `idt_register` which is
just a memory address and a size, similar to the GDT register.
Finally, we define a couple variables to access those data structures
from assembler code.
`cpu/idt.c` just fills in every struct with a handler.
`cpu/idt.c` just fills in every struct with a handler.
As you can see, it is a matter
of setting the struct values and calling the `lidt` assembler command.
@ -51,8 +54,8 @@ of setting the struct values and calling the `lidt` assembler command.
ISRs
----
The Interrupt Service Routines run every time the CPU detects an
interrupt, which is usually fatal.
The Interrupt Service Routines run every time the CPU detects an
interrupt, which is usually fatal.
We will write just enough code to handle them, print an error message,
and halt the CPU.

@ -1,10 +1,13 @@
Interrupts: IRQs
================
*Concepts you may want to Google beforehand: IRQs, PIC, polling*
**Goal: Finish the interrupts implementation and CPU timer**
When the CPU boots, the PIC maps IRQs 0-7 to INT 0x8-0xF
and IRQs 8-15 to INT 0x70-0x77. This conflicts with the ISRs
we programmed last lesson. Since we programmed ISRs 0-31,
we programmed last lesson. Since we programmed ISRs 0-31,
it is standard to remap the IRQs to ISRs 32-47.
The PICs are communicated with via I/O ports (see lesson 15).
@ -12,13 +15,13 @@ The Master PIC has command 0x20 and data 0x21, while the slave has
command 0xA0 and data 0xA1.
The code for remapping the PICs is weird and includes
some masks, so check
some masks, so check
[this article](http://www.osdev.org/wiki/PIC) if you're curious.
Otherwise, just look at `cpu/isr.c`, new code after we set the IDT
gates for the ISRs. After that, we add the IDT gates for IRQs.
Now we jump to assembler, at `interrupt.asm`. The first task is to
add global definitions for the IRQ symbols we just used in the C code.
add global definitions for the IRQ symbols we just used in the C code.
Look at the end of the `global` statements.
Then, add the IRQ handlers. Same `interrupt.asm`, at the bottom. Notice
@ -31,7 +34,7 @@ a new `[extern irq_handler]`
Now back to C code, to write the `irq_handler()` in `isr.c`. It sends some
EOIs to the PICs and calls the appropriate handler, which is stored in an array
named `interrupt_handlers` and defined at the top of the file. The new structs
are defined in `isr.h`. We will also use a simple function to register
are defined in `isr.h`. We will also use a simple function to register
the interrupt handlers.
That was a lot of work, but now we can define our first IRQ handler!

@ -1,3 +1,6 @@
Interrupts: Timer
=================
*Concepts you may want to Google beforehand: CPU timer, keyboard interrupts, scancode*
**Goal: Implement our first IRQ handlers: the CPU timer and the keyboard**

@ -1,7 +1,9 @@
Shell
=====
**Goal: Clean the code a bit and parse user input**
In this lesson we will do two things. First, we will clean up the code a bit, so it is ready
In this lesson we will do two things. First, we will clean up the code a bit, so it is ready
for further lessons. During the previous ones I tried to put things in the most predictable places,
but it is also a good exercise to know when the code base is growing and adapt it to current
and further needs.
@ -52,7 +54,7 @@ arrays which are defined at the beginning of `keyboard.c`
- When the OS wants to read user input, it calls `libc/io.c:readline()`
`keyboard.c` also parses backspace, by removing the last element
of the key buffer, and deleting it from the screen, by calling
of the key buffer, and deleting it from the screen, by calling
`screen.c:kprint_backspace()`. For this we needed to modify a bit
`print_char()` to not advance the offset when printing a backspace

@ -1,8 +1,11 @@
malloc
======
*Concepts you may want to Google beforehand: malloc*
**Goal: Implement a memory allocator**
We will add a kernel memory allocator to `libc/mem.c`. It is
We will add a kernel memory allocator to `libc/mem.c`. It is
implemented as a simple pointer to free memory, which keeps
growing.
@ -18,7 +21,7 @@ aligned 4096 bytes or 0x1000 from the previous one.
Note that we added a new `strings.c:hex_to_ascii()` for
nicer printing of hex numbers.
Another cosmetic modification is to rename `types.c` to
Another cosmetic modification is to rename `types.c` to
`type.c` for language consistency.
The rest of the files are unchanged from last lesson.

@ -1,3 +1,6 @@
Fixes
=====
*Concepts you may want to Google beforehand: freestanding, uint32_t, size_t*
**Goal: Fix miscellaneous issues with our code**
@ -22,7 +25,7 @@ it for linking. Since this is tricky, we'll delete `-nostdlib`
2. kernel.c `main()` function
-----------------------------
Modify `kernel/kernel.c` and change `main()` to `kernel_main()` since gcc recognizes "main" as
Modify `kernel/kernel.c` and change `main()` to `kernel_main()` since gcc recognizes "main" as
a special keyword and we don't want to mess with that.
Change `boot/kernel_entry.asm` to point to the new name accordingly.
@ -48,7 +51,7 @@ We also delete the underscores around `__asm__` and `__volatile__` since they ar
First, since `kmalloc` uses a size parameter, we'll use the correct data type `size_t` instead
of `u32int_t`. `size_t` should be used for all parameters which "count" stuff and cannot be
negative. Include `<stddef.h>`.
negative. Include `<stddef.h>`.
We will fix our `kmalloc` in the future, making it a proper memory manager and aligning data types.
For now, it will always return a new page-aligned memory block.
@ -65,12 +68,12 @@ We will implement the missing `mem*` functions in following lessons
`cli` is redundant, since we already established on the IDT entries if interrupts
are enabled within a handler using the `idt_gate_t` flags.
`sti` is also redundant, as `iret` loads the eflags value from the stack, which contains a
`sti` is also redundant, as `iret` loads the eflags value from the stack, which contains a
bit telling whether interrupts are on or off.
In other words the interrupt handler automatically restores interrupts whether or not
In other words the interrupt handler automatically restores interrupts whether or not
interrupts were enabled before this interrupt
On `cpu/isr.h`, `struct registers_t` has a couple issues.
On `cpu/isr.h`, `struct registers_t` has a couple issues.
First, the alleged `esp` is renamed to `useless`.
The value is useless because it has to do with the current stack context, not what was interrupted.
Then, we rename `useresp` to `esp`

@ -1,3 +1,6 @@
El Capitan
==========
**Goal: Update our build system to El Capitan**
If you were following this guide from the beginning and upgraded to El Capitan only
@ -56,10 +59,10 @@ tar xf gcc-4.9.1.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-4.9.1/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc
```

@ -0,0 +1,191 @@
$if(context-lang)$
\mainlanguage[$context-lang$]
$endif$
$if(context-dir)$
\setupalign[$context-dir$]
\setupdirections[bidi=on,method=two]
$endif$
% Enable hyperlinks
\setupinteraction
[state=start,
$if(title)$
title={$title$},
$endif$
$if(subtitle)$
subtitle={$subtitle$},
$endif$
$if(author)$
author={$for(author)$$author$$sep$; $endfor$},
$endif$
$if(keywords)$
keyword={$for(keywords)$$keywords$$sep$; $endfor$},
$endif$
style=$linkstyle$,
color=$linkcolor$,
contrastcolor=$linkcontrastcolor$]
\setupurl[style=$urlstyle$]
% make chapter, section bookmarks visible when opening document
\placebookmarks[chapter, section, subsection, subsubsection, subsubsubsection, subsubsubsubsection][chapter, section]
\setupinteractionscreen[option={bookmark,title}]
$if(papersize)$
\setuppapersize[$for(papersize)$$papersize$$sep$,$endfor$]
$endif$
$if(layout)$
\setuplayout[$for(layout)$$layout$$sep$,$endfor$]
$endif$
$if(pagenumbering)$
\setuppagenumbering[$for(pagenumbering)$$pagenumbering$$sep$,$endfor$]
$else$
\setuppagenumbering[location={footer,middle}]
$endif$
$if(pdfa)$
% attempt to generate PDF/A
\setupbackend
[format=PDF/A-$pdfa$,
profile={$if(pdfaiccprofile)$$for(pdfaiccprofile)$$pdfaiccprofile$$sep$,$endfor$$else$sRGB.icc$endif$},
intent=$if(pdfaintent)$$pdfaintent$$else$sRGB IEC61966-2.1$endif$]
$endif$
\setupbackend[export=yes]
\setupstructure[state=start,method=auto]
% use microtypography
\definefontfeature[default][default][script=latn, protrusion=quality, expansion=quality, itlc=yes, textitalics=yes, onum=yes, pnum=yes]
\definefontfeature[default:tnum][default][tnum=yes, pnum=no]
\definefontfeature[smallcaps][script=latn, protrusion=quality, expansion=quality, smcp=yes, onum=yes, pnum=yes]
\setupalign[hz,hanging]
\setupitaliccorrection[global, always]
\setupbodyfontenvironment[default][em=italic] % use italic as em, not slanted
\definefallbackfamily[mainface][rm][CMU Serif][preset=range:greek, force=yes]
\definefontfamily[mainface][rm][$if(mainfont)$$mainfont$$else$Latin Modern Roman$endif$]
\definefontfamily[mainface][mm][$if(mathfont)$$mathfont$$else$Latin Modern Math$endif$]
\definefontfamily[mainface][ss][$if(sansfont)$$sansfont$$else$Latin Modern Sans$endif$]
\definefontfamily[mainface][tt][$if(monofont)$$monofont$$else$Latin Modern Typewriter$endif$][features=none]
\setupbodyfont[mainface$if(fontsize)$,$fontsize$$endif$]
\setupwhitespace[$if(whitespace)$$whitespace$$else$medium$endif$]
$if(indenting)$
\setupindenting[$for(indenting)$$indenting$$sep$,$endfor$]
$endif$
$if(interlinespace)$
\setupinterlinespace[$for(interlinespace)$$interlinespace$$sep$,$endfor$]
$endif$
\setuphead[chapter] [style=\tfd\setupinterlinespace,header=empty]
\setuphead[section] [style=\tfc\setupinterlinespace]
\setuphead[subsection] [style=\tfb\setupinterlinespace]
\setuphead[subsubsection] [style=\bf]
\setuphead[subsubsubsection] [style=\sc]
\setuphead[subsubsubsubsection][style=\it]
\definesectionlevels
[default]
[section, subsection, subsubsection, subsubsubsection, subsubsubsubsection]
$if(headertext)$
\setupheadertexts$for(headertext)$[$headertext$]$endfor$
$endif$
$if(footertext)$
\setupfootertexts$for(footertext)$[$footertext$]$endfor$
$endif$
$if(number-sections)$
$else$
\setuphead[chapter, section, subsection, subsubsection, subsubsubsection, subsubsubsubsection][number=no]
$endif$
\definedescription
[description]
[headstyle=bold, style=normal, location=hanging, width=broad, margin=1cm, alternative=hanging]
\setupitemize[autointro] % prevent orphan list intro
\setupitemize[indentnext=no]
\defineitemgroup[enumerate]
\setupenumerate[each][fit][itemalign=left,distance=.5em,style={\feature[+][default:tnum]}]
\setupfloat[figure][default={here,nonumber}]
\setupfloat[table][default={here,nonumber}]
\setupxtable[frame=off]
\setupxtable[head][topframe=on]
\setupxtable[body][]
\setupxtable[foot][]
\setupxtable[lastrow][bottomframe=on]
$if(emphasis-commands)$
$emphasis-commands$
$endif$
$if(highlighting-commands)$
$highlighting-commands$
$endif$
$if(csl-refs)$
\definemeasure[cslhangindent][1.5em]
\definenarrower[hangingreferences][left=\measure{cslhangindent}]
\definestartstop [cslreferences] [
$if(csl-hanging-indent)$
before={%
\starthangingreferences[left]
\setupindenting[-\leftskip,yes,first]
\doindentation
},
after=\stophangingreferences,
$endif$
]
$endif$
$if(includesource)$
$for(sourcefile)$
\attachment[file=$curdir$/$sourcefile$,method=hidden]
$endfor$
$endif$
$for(header-includes)$
$header-includes$
$endfor$
\starttext
$if(title)$
\startalignment[middle]
{\tfd\setupinterlinespace $title$}
$if(subtitle)$
\smallskip
{\tfa\setupinterlinespace $subtitle$}
$endif$
$if(author)$
\smallskip
{\tfa\setupinterlinespace $for(author)$$author$$sep$\crlf $endfor$}
$endif$
$if(date)$
\smallskip
{\tfa\setupinterlinespace $date$}
$endif$
\bigskip
\stopalignment
$endif$
$if(abstract)$
\midaligned{\it Abstract}
\startnarrower[2*middle]
$abstract$
\stopnarrower
\blank[big]
$endif$
$for(include-before)$
$include-before$
$endfor$
$if(toc)$
\completecontent
$endif$
$if(lof)$
\completelistoffigures
$endif$
$if(lot)$
\completelistoftables
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
\stoptext

@ -0,0 +1,52 @@
from: gfm
input-files:
- ${.}/README.md
- ${.}/00-environment/README.md
- ${.}/01-bootsector-barebones/README.md
- ${.}/02-bootsector-print/README.md
- ${.}/03-bootsector-memory/README.md
- ${.}/04-bootsector-stack/README.md
- ${.}/05-bootsector-functions-strings/README.md
- ${.}/06-bootsector-segmentation/README.md
- ${.}/07-bootsector-disk/README.md
- ${.}/08-32bit-print/README.md
- ${.}/09-32bit-gdt/README.md
- ${.}/10-32bit-enter/README.md
- ${.}/11-kernel-crosscompiler/README.md
- ${.}/12-kernel-c/README.md
- ${.}/13-kernel-barebones/README.md
- ${.}/14-checkpoint/README.md
- ${.}/15-video-ports/README.md
- ${.}/16-video-driver/README.md
- ${.}/17-video-scroll/README.md
- ${.}/18-interrupts/README.md
- ${.}/19-interrupts-irqs/README.md
- ${.}/20-interrupts-timer/README.md
- ${.}/21-shell/README.md
- ${.}/22-malloc/README.md
- ${.}/23-fixes/README.md
- ${.}/24-el-capitan/README.md
to: pdf
pdf-engine: context
output-file: os-tutorial.pdf
variables:
title: OS Tutorial
subtitle: How to create an OS from scratch
author: Carlos Fenollosa
includesource: true
papersize: letter
pdfa: true
linkcolor: blue
linkcontrastcolor: red
file-scope: true
verbosity: ERROR
tab-stop: 4
template: ./pandoc.context
dpi: 300
toc: true
top-level-division: chapter
reference-location: section
number-sections: true
Loading…
Cancel
Save