{"id":298,"date":"2009-09-07T18:15:20","date_gmt":"2009-09-08T02:15:20","guid":{"rendered":"http:\/\/www.pagetable.com\/?p=298"},"modified":"2009-09-07T18:15:20","modified_gmt":"2009-09-08T02:15:20","slug":"a-standalone-printf-for-early-bootup","status":"publish","type":"post","link":"https:\/\/www.pagetable.com\/?p=298","title":{"rendered":"A Standalone printf() for Early Bootup"},"content":{"rendered":"<p>A while ago, I complained about operating systems with overly complicated startup code that <a href=\"http:\/\/www.pagetable.com\/?p=276\">spends too much time in assembly<\/a> and does hot have printf() or framebuffer access until very late.<\/p>\n<p>This second post is about printf(): Many systems use POST codes (on i386\/x86_64, i.e. writes to port 0x80) or debug LED for debugging, or have complicated and cumbersome implementations for puts() and print_hex() &#8211; and printf() is only available very late, because it has some special requirement, like console channels being set up. But printf() is not rocket science: Al it needs is C and a stack. Whatever system you are bringing up on whatever platform: Having printf() as early as possible will prove very useful.<\/p>\n<p>Today, I am presenting a full-featured standalone version of printf() that can be added to arbitrary 32 or 64 bit C code. The code has been taken from FreeBSD (<a href=\"http:\/\/www.freebsd.org\/cgi\/cvsweb.cgi\/src\/sys\/kern\/subr_prf.c\">sys\/kern\/subr_prf.c<\/a>) and is therefore BSD-licensed. Unnecessary functions have been removed and all typedefs required have been added.<\/p>\n<pre>\n\/*-\n * Copyright (c) 1986, 1988, 1991, 1993\n *\tThe Regents of the University of California.  All rights reserved.\n * (c) UNIX System Laboratories, Inc.\n * All or some portions of this file are derived from material licensed\n * to the University of California by American Telephone and Telegraph\n * Co. or Unix System Laboratories, Inc. and are reproduced herein with\n * the permission of UNIX System Laboratories, Inc.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and\/or other materials provided with the distribution.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *\t@(#)subr_prf.c\t8.3 (Berkeley) 1\/21\/94\n *\/\n\ntypedef unsigned long size_t;\ntypedef long ssize_t;\n#ifdef __64BIT__\ntypedef unsigned long long uintmax_t;\ntypedef long long intmax_t;\n#else\ntypedef unsigned int uintmax_t;\ntypedef int intmax_t;\n#endif\ntypedef unsigned char u_char;\ntypedef unsigned int u_int;\ntypedef unsigned long u_long;\ntypedef unsigned short u_short;\ntypedef unsigned long long u_quad_t;\ntypedef long long quad_t;\ntypedef unsigned long uintptr_t;\ntypedef long ptrdiff_t;\n#define NULL ((void*)0)\n#define NBBY    8               \/* number of bits in a byte *\/\nchar const hex2ascii_data[] = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n#define hex2ascii(hex)  (hex2ascii_data[hex])\n#define va_list __builtin_va_list\n#define va_start __builtin_va_start\n#define va_arg __builtin_va_arg\n#define va_end __builtin_va_end\n#define toupper(c)      ((c) - 0x20 * (((c) >= 'a') && ((c) <= 'z')))\nstatic size_t\nstrlen(const char *s)\n{\n\tsize_t l = 0;\n\twhile (*s++)\n\t\tl++;\n\treturn l;\n}\n\n\/* Max number conversion buffer length: a u_quad_t in base 2, plus NUL byte. *\/\n#define MAXNBUF\t(sizeof(intmax_t) * NBBY + 1)\n\n\/*\n * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse\n * order; return an optional length and a pointer to the last character\n * written in the buffer (i.e., the first character of the string).\n * The buffer pointed to by `nbuf' must have length >= MAXNBUF.\n *\/\nstatic char *\nksprintn(char *nbuf, uintmax_t num, int base, int *lenp, int upper)\n{\n\tchar *p, c;\n\n\tp = nbuf;\n\t*p = '\ufffd';\n\tdo {\n\t\tc = hex2ascii(num % base);\n\t\t*++p = upper ? toupper(c) : c;\n\t} while (num \/= base);\n\tif (lenp)\n\t\t*lenp = p - nbuf;\n\treturn (p);\n}\n\n\/*\n * Scaled down version of printf(3).\n *\n * Two additional formats:\n *\n * The format %b is supported to decode error registers.\n * Its usage is:\n *\n *\tprintf(\"reg=%bn\", regval, \"<base><arg>*\");\n *\n * where <base> is the output base expressed as a control character, e.g.\n * 10 gives octal; 20 gives hex.  Each arg is a sequence of characters,\n * the first of which gives the bit number to be inspected (origin 1), and\n * the next characters (up to a control character, i.e. a character <= 32),\n * give the name of the register.  Thus:\n *\n *\tkvprintf(\"reg=%bn\", 3, \"102BITTWO1BITONEn\");\n *\n * would produce output:\n *\n *\treg=3<bittwo,BITONE>\n *\n * XXX:  %D  -- Hexdump, takes pointer and separator string:\n *\t\t(\"%6D\", ptr, \":\")   -> XX:XX:XX:XX:XX:XX\n *\t\t(\"%*D\", len, ptr, \" \" -> XX XX XX XX ...\n *\/\nint\nkvprintf(char const *fmt, void (*func)(int, void*), void *arg, int radix, va_list ap)\n{\n#define PCHAR(c) {int cc=(c); if (func) (*func)(cc,arg); else *d++ = cc; retval++; }\n\tchar nbuf[MAXNBUF];\n\tchar *d;\n\tconst char *p, *percent, *q;\n\tu_char *up;\n\tint ch, n;\n\tuintmax_t num;\n\tint base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot;\n\tint cflag, hflag, jflag, tflag, zflag;\n\tint dwidth, upper;\n\tchar padc;\n\tint stop = 0, retval = 0;\n\n\tnum = 0;\n\tif (!func)\n\t\td = (char *) arg;\n\telse\n\t\td = NULL;\n\n\tif (fmt == NULL)\n\t\tfmt = \"(fmt null)n\";\n\n\tif (radix < 2 || radix > 36)\n\t\tradix = 10;\n\n\tfor (;;) {\n\t\tpadc = ' ';\n\t\twidth = 0;\n\t\twhile ((ch = (u_char)*fmt++) != '%' || stop) {\n\t\t\tif (ch == '\ufffd')\n\t\t\t\treturn (retval);\n\t\t\tPCHAR(ch);\n\t\t}\n\t\tpercent = fmt - 1;\n\t\tqflag = 0; lflag = 0; ladjust = 0; sharpflag = 0; neg = 0;\n\t\tsign = 0; dot = 0; dwidth = 0; upper = 0;\n\t\tcflag = 0; hflag = 0; jflag = 0; tflag = 0; zflag = 0;\nreswitch:\tswitch (ch = (u_char)*fmt++) {\n\t\tcase '.':\n\t\t\tdot = 1;\n\t\t\tgoto reswitch;\n\t\tcase '#':\n\t\t\tsharpflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase '+':\n\t\t\tsign = 1;\n\t\t\tgoto reswitch;\n\t\tcase '-':\n\t\t\tladjust = 1;\n\t\t\tgoto reswitch;\n\t\tcase '%':\n\t\t\tPCHAR(ch);\n\t\t\tbreak;\n\t\tcase '*':\n\t\t\tif (!dot) {\n\t\t\t\twidth = va_arg(ap, int);\n\t\t\t\tif (width < 0) {\n\t\t\t\t\tladjust = !ladjust;\n\t\t\t\t\twidth = -width;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdwidth = va_arg(ap, int);\n\t\t\t}\n\t\t\tgoto reswitch;\n\t\tcase '0':\n\t\t\tif (!dot) {\n\t\t\t\tpadc = '0';\n\t\t\t\tgoto reswitch;\n\t\t\t}\n\t\tcase '1': case '2': case '3': case '4':\n\t\tcase '5': case '6': case '7': case '8': case '9':\n\t\t\t\tfor (n = 0;; ++fmt) {\n\t\t\t\t\tn = n * 10 + ch - '0';\n\t\t\t\t\tch = *fmt;\n\t\t\t\t\tif (ch < '0' || ch > '9')\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (dot)\n\t\t\t\tdwidth = n;\n\t\t\telse\n\t\t\t\twidth = n;\n\t\t\tgoto reswitch;\n\t\tcase 'b':\n\t\t\tnum = (u_int)va_arg(ap, int);\n\t\t\tp = va_arg(ap, char *);\n\t\t\tfor (q = ksprintn(nbuf, num, *p++, NULL, 0); *q;)\n\t\t\t\tPCHAR(*q--);\n\n\t\t\tif (num == 0)\n\t\t\t\tbreak;\n\n\t\t\tfor (tmp = 0; *p;) {\n\t\t\t\tn = *p++;\n\t\t\t\tif (num & (1 << (n - 1))) {\n\t\t\t\t\tPCHAR(tmp ? ',' : '<');\n\t\t\t\t\tfor (; (n = *p) > ' '; ++p)\n\t\t\t\t\t\tPCHAR(n);\n\t\t\t\t\ttmp = 1;\n\t\t\t\t} else\n\t\t\t\t\tfor (; *p > ' '; ++p)\n\t\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (tmp)\n\t\t\t\tPCHAR('>');\n\t\t\tbreak;\n\t\tcase 'c':\n\t\t\tPCHAR(va_arg(ap, int));\n\t\t\tbreak;\n\t\tcase 'D':\n\t\t\tup = va_arg(ap, u_char *);\n\t\t\tp = va_arg(ap, char *);\n\t\t\tif (!width)\n\t\t\t\twidth = 16;\n\t\t\twhile(width--) {\n\t\t\t\tPCHAR(hex2ascii(*up >> 4));\n\t\t\t\tPCHAR(hex2ascii(*up & 0x0f));\n\t\t\t\tup++;\n\t\t\t\tif (width)\n\t\t\t\t\tfor (q=p;*q;q++)\n\t\t\t\t\t\tPCHAR(*q);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'd':\n\t\tcase 'i':\n\t\t\tbase = 10;\n\t\t\tsign = 1;\n\t\t\tgoto handle_sign;\n\t\tcase 'h':\n\t\t\tif (hflag) {\n\t\t\t\thflag = 0;\n\t\t\t\tcflag = 1;\n\t\t\t} else\n\t\t\t\thflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase 'j':\n\t\t\tjflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase 'l':\n\t\t\tif (lflag) {\n\t\t\t\tlflag = 0;\n\t\t\t\tqflag = 1;\n\t\t\t} else\n\t\t\t\tlflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase 'n':\n\t\t\tif (jflag)\n\t\t\t\t*(va_arg(ap, intmax_t *)) = retval;\n\t\t\telse if (qflag)\n\t\t\t\t*(va_arg(ap, quad_t *)) = retval;\n\t\t\telse if (lflag)\n\t\t\t\t*(va_arg(ap, long *)) = retval;\n\t\t\telse if (zflag)\n\t\t\t\t*(va_arg(ap, size_t *)) = retval;\n\t\t\telse if (hflag)\n\t\t\t\t*(va_arg(ap, short *)) = retval;\n\t\t\telse if (cflag)\n\t\t\t\t*(va_arg(ap, char *)) = retval;\n\t\t\telse\n\t\t\t\t*(va_arg(ap, int *)) = retval;\n\t\t\tbreak;\n\t\tcase 'o':\n\t\t\tbase = 8;\n\t\t\tgoto handle_nosign;\n\t\tcase 'p':\n\t\t\tbase = 16;\n\t\t\tsharpflag = (width == 0);\n\t\t\tsign = 0;\n\t\t\tnum = (uintptr_t)va_arg(ap, void *);\n\t\t\tgoto number;\n\t\tcase 'q':\n\t\t\tqflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase 'r':\n\t\t\tbase = radix;\n\t\t\tif (sign)\n\t\t\t\tgoto handle_sign;\n\t\t\tgoto handle_nosign;\n\t\tcase 's':\n\t\t\tp = va_arg(ap, char *);\n\t\t\tif (p == NULL)\n\t\t\t\tp = \"(null)\";\n\t\t\tif (!dot)\n\t\t\t\tn = strlen (p);\n\t\t\telse\n\t\t\t\tfor (n = 0; n < dwidth &#038;&#038; p[n]; n++)\n\t\t\t\t\tcontinue;\n\n\t\t\twidth -= n;\n\n\t\t\tif (!ladjust &#038;&#038; width > 0)\n\t\t\t\twhile (width--)\n\t\t\t\t\tPCHAR(padc);\n\t\t\twhile (n--)\n\t\t\t\tPCHAR(*p++);\n\t\t\tif (ladjust && width > 0)\n\t\t\t\twhile (width--)\n\t\t\t\t\tPCHAR(padc);\n\t\t\tbreak;\n\t\tcase 't':\n\t\t\ttflag = 1;\n\t\t\tgoto reswitch;\n\t\tcase 'u':\n\t\t\tbase = 10;\n\t\t\tgoto handle_nosign;\n\t\tcase 'X':\n\t\t\tupper = 1;\n\t\tcase 'x':\n\t\t\tbase = 16;\n\t\t\tgoto handle_nosign;\n\t\tcase 'y':\n\t\t\tbase = 16;\n\t\t\tsign = 1;\n\t\t\tgoto handle_sign;\n\t\tcase 'z':\n\t\t\tzflag = 1;\n\t\t\tgoto reswitch;\nhandle_nosign:\n\t\t\tsign = 0;\n\t\t\tif (jflag)\n\t\t\t\tnum = va_arg(ap, uintmax_t);\n\t\t\telse if (qflag)\n\t\t\t\tnum = va_arg(ap, u_quad_t);\n\t\t\telse if (tflag)\n\t\t\t\tnum = va_arg(ap, ptrdiff_t);\n\t\t\telse if (lflag)\n\t\t\t\tnum = va_arg(ap, u_long);\n\t\t\telse if (zflag)\n\t\t\t\tnum = va_arg(ap, size_t);\n\t\t\telse if (hflag)\n\t\t\t\tnum = (u_short)va_arg(ap, int);\n\t\t\telse if (cflag)\n\t\t\t\tnum = (u_char)va_arg(ap, int);\n\t\t\telse\n\t\t\t\tnum = va_arg(ap, u_int);\n\t\t\tgoto number;\nhandle_sign:\n\t\t\tif (jflag)\n\t\t\t\tnum = va_arg(ap, intmax_t);\n\t\t\telse if (qflag)\n\t\t\t\tnum = va_arg(ap, quad_t);\n\t\t\telse if (tflag)\n\t\t\t\tnum = va_arg(ap, ptrdiff_t);\n\t\t\telse if (lflag)\n\t\t\t\tnum = va_arg(ap, long);\n\t\t\telse if (zflag)\n\t\t\t\tnum = va_arg(ap, ssize_t);\n\t\t\telse if (hflag)\n\t\t\t\tnum = (short)va_arg(ap, int);\n\t\t\telse if (cflag)\n\t\t\t\tnum = (char)va_arg(ap, int);\n\t\t\telse\n\t\t\t\tnum = va_arg(ap, int);\nnumber:\n\t\t\tif (sign && (intmax_t)num < 0) {\n\t\t\t\tneg = 1;\n\t\t\t\tnum = -(intmax_t)num;\n\t\t\t}\n\t\t\tp = ksprintn(nbuf, num, base, &#038;tmp, upper);\n\t\t\tif (sharpflag &#038;&#038; num != 0) {\n\t\t\t\tif (base == 8)\n\t\t\t\t\ttmp++;\n\t\t\t\telse if (base == 16)\n\t\t\t\t\ttmp += 2;\n\t\t\t}\n\t\t\tif (neg)\n\t\t\t\ttmp++;\n\n\t\t\tif (!ladjust &#038;&#038; padc != '0' &#038;&#038; width\n\t\t\t    &#038;&#038; (width -= tmp) > 0)\n\t\t\t\twhile (width--)\n\t\t\t\t\tPCHAR(padc);\n\t\t\tif (neg)\n\t\t\t\tPCHAR('-');\n\t\t\tif (sharpflag && num != 0) {\n\t\t\t\tif (base == 8) {\n\t\t\t\t\tPCHAR('0');\n\t\t\t\t} else if (base == 16) {\n\t\t\t\t\tPCHAR('0');\n\t\t\t\t\tPCHAR('x');\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!ladjust && width && (width -= tmp) > 0)\n\t\t\t\twhile (width--)\n\t\t\t\t\tPCHAR(padc);\n\n\t\t\twhile (*p)\n\t\t\t\tPCHAR(*p--);\n\n\t\t\tif (ladjust && width && (width -= tmp) > 0)\n\t\t\t\twhile (width--)\n\t\t\t\t\tPCHAR(padc);\n\n\t\t\tbreak;\n\t\tdefault:\n\t\t\twhile (percent < fmt)\n\t\t\t\tPCHAR(*percent++);\n\t\t\t\/*\n\t\t\t * Since we ignore an formatting argument it is no\n\t\t\t * longer safe to obey the remaining formatting\n\t\t\t * arguments as the arguments will no longer match\n\t\t\t * the format specs.\n\t\t\t *\/\n\t\t\tstop = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n#undef PCHAR\n}\n\nstatic void\nputchar(int c, void *arg)\n{\n\t\/* add your putchar here *\/\n}\n\nvoid\nprintf(const char *fmt, ...)\n{\n\t\/* http:\/\/www.pagetable.com\/?p=298 *\/\n\tva_list ap;\n\n\tva_start(ap, fmt);\n\tkvprintf(fmt, putchar, NULL, 10, ap);\n\tva_end(ap);\n}\n<\/pre>\n<p>One thing you will have to do is #define or #undef __64__BIT to enable or disable support for printing 64 bit values. If you are on a 64 bit architecture, you will need this to print addresses properly. If you are on 32 bit, this support will require external library functions to do 64 bit arithmetic, so you probably want to turn this off.<\/p>\n<p>On ARM, which doesn't have an assembly statement for division, will always need a library routine. You can use FreeBSD's at <a href=\"http:\/\/www.freebsd.org\/cgi\/cvsweb.cgi\/src\/sys\/libkern\/arm\/divsi3.S\">sys\/libkern\/arm\/divsi3.S<\/a>, if you to add the following #defines to make the file compile:<\/p>\n<pre>\n#define __FBSDID(a)\n#define RET bx lr\n#define ENTRY_NP(a) _##a: .global _##a\n#define _KERNEL\n<\/pre>\n<p>All you need to do now to get printf() working is to fill the putchar() function, for example with a call to a serial_putc() implementation, if you have a serial port. On a PC, serial_putc() could look like this:<\/p>\n<pre>\nstatic void outb(short p, unsigned char a)\n{\n        asm volatile(\"outb %b0, %w1\" : : \"a\" (a), \"Nd\" (p));\n}\nstatic unsigned char inb(short p)\n{\n        unsigned char a;\n        asm volatile(\"inb %w1, %b0\" : \"=a\" (a) : \"Nd\" (p));\n        return a;\n}\n#define SERIAL_PORT 0x3f8\nstatic void\nserial_putc(unsigned char c) {\n\twhile (!(inb(SERIAL_PORT + 5) & 0x20));\n\toutb(SERIAL_PORT, c);\n}\n<\/pre>\n<p>On an \"ARM Integrator\/CP\" board, the default for <a href=\"http:\/\/www.qemu.org\/\">QEMU<\/a> emulating ARM, the follwing serial_putc() would apply (make sure you are running the MMU off, or with the serial port MMIO region mapped):<\/p>\n<pre>\n#define SERIAL_PORT 0x16000000\nstatic void\nserial_putc(unsigned char c) {\n\twhile ((*(volatile unsigned int *)(SERIAL_PORT + 0x18)) & 0x20);\n\t*(volatile unsigned int *)(SERIAL_PORT) = (c);\n}\n<\/pre>\n<p>Unfortunately, sometimes you are bringing up or debugging a system that does not have a serial port - or at least you haven't found one: Apple Macintosh and various game systems come to mind. In this case, you might be able to set up a framebuffer and print to that. In a later post, I will present a small standalone framebuffer library.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while ago, I complained about operating systems with overly complicated startup code that spends too much time in assembly and does hot have printf() or framebuffer access until very late. This second post is about printf(): Many systems use POST codes (on i386\/x86_64, i.e. writes to port 0x80) or debug LED for debugging, or &#8230; <a title=\"A Standalone printf() for Early Bootup\" class=\"read-more\" href=\"https:\/\/www.pagetable.com\/?p=298\" aria-label=\"Read more about A Standalone printf() for Early Bootup\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22,32,38],"tags":[],"class_list":["post-298","post","type-post","status-publish","format-standard","hentry","category-operating-systems","category-tricks","category-x86"],"_links":{"self":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts\/298","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=298"}],"version-history":[{"count":0,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts\/298\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}