sdl: remove deprecated API usage

In addition framebuffer resizing is now also supported.

Fixes #2583.
This commit is contained in:
Josef Söntgen 2017-11-17 13:03:22 +01:00 committed by Christian Helmuth
parent 86c1f65dfe
commit 6ca8f4c174
12 changed files with 372 additions and 93 deletions

View File

@ -0,0 +1,5 @@
SRC_CC = sdl_main.cc
LIBS += libc
vpath sdl_main.cc $(REP_DIR)/src/lib/sdl

View File

@ -115,7 +115,7 @@ append boot_modules {
core init timer } [audio_drv_binary] { avplay core init timer } [audio_drv_binary] { avplay
ld.lib.so libc.lib.so libm.lib.so pthread.lib.so zlib.lib.so sdl.lib.so ld.lib.so libc.lib.so libm.lib.so pthread.lib.so zlib.lib.so sdl.lib.so
avfilter.lib.so avutil.lib.so avcodec.lib.so avformat.lib.so swscale.lib.so avfilter.lib.so avutil.lib.so avcodec.lib.so avformat.lib.so swscale.lib.so
avresample.lib.so posix.lib.so avresample.lib.so
mediafile mediafile
} }

View File

@ -85,7 +85,7 @@ set boot_modules {
core init core init
timer timer
test-sdl test-sdl
ld.lib.so libc.lib.so libm.lib.so sdl.lib.so pthread.lib.so posix.lib.so ld.lib.so libc.lib.so libm.lib.so sdl.lib.so pthread.lib.so
} }
# platform-specific modules # platform-specific modules

View File

@ -3,7 +3,7 @@ include $(REP_DIR)/lib/import/import-av.inc
TARGET = avplay TARGET = avplay
SRC_C = avplay.c cmdutils.c libc_dummies.c SRC_C = avplay.c cmdutils.c libc_dummies.c
LIBS += avfilter avformat avcodec avutil avresample swscale LIBS += avfilter avformat avcodec avutil avresample swscale
LIBS += sdl posix LIBS += sdl sdlmain libc libm
CC_WARN += -Wno-parentheses -Wno-switch -Wno-uninitialized \ CC_WARN += -Wno-parentheses -Wno-switch -Wno-uninitialized \
-Wno-format-zero-length -Wno-pointer-sign -Wno-format-zero-length -Wno-pointer-sign

View File

@ -0,0 +1,24 @@
/*
* \brief Genode-specific data structures
* \author Josef Soentgen
* \date 2017-11-21
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SDL_GENODE_INTERNAL_H_
#define _SDL_GENODE_INTERNAL_H_
struct Video
{
bool resize_pending;
int width;
int height;
};
#endif /* _SDL_GENODE_INTERNAL_H_ */

View File

@ -14,6 +14,7 @@
* under the terms of the GNU Affero General Public License version 3. * under the terms of the GNU Affero General Public License version 3.
*/ */
/* Genode includes */
#include <base/allocator_avl.h> #include <base/allocator_avl.h>
#include <base/attached_rom_dataspace.h> #include <base/attached_rom_dataspace.h>
#include <base/log.h> #include <base/log.h>
@ -21,6 +22,13 @@
#include <audio_out_session/connection.h> #include <audio_out_session/connection.h>
#include <util/reconstructible.h> #include <util/reconstructible.h>
/* local includes */
#include <SDL_genode_internal.h>
extern Genode::Env *global_env();
extern Genode::Lock event_lock;
enum { enum {
AUDIO_CHANNELS = 2, AUDIO_CHANNELS = 2,
@ -34,7 +42,6 @@ using Genode::Hex;
using Genode::Constructible; using Genode::Constructible;
static const char *channel_names[] = { "front left", "front right" }; static const char *channel_names[] = { "front left", "front right" };
static float volume = 1.0;
static Signal_context config_signal_context; static Signal_context config_signal_context;
extern "C" { extern "C" {
@ -54,48 +61,55 @@ extern "C" {
/* The tag name used by Genode audio */ /* The tag name used by Genode audio */
#define GENODEAUD_DRIVER_NAME "genode" #define GENODEAUD_DRIVER_NAME "genode"
struct Volume_config
{
Genode::Env &_env;
Genode::Attached_rom_dataspace _config_rom { _env, "config" };
float volume { 1.0f };
void _handle_config_update()
{
_config_rom.update();
if (!_config_rom.valid()) { return; }
Genode::Lock_guard<Genode::Lock> guard(event_lock);
Genode::Xml_node config = _config_rom.xml();
try {
unsigned int config_volume;
config.sub_node("sdl_audio_volume").attribute("value")
.value(&config_volume);
volume = (float)config_volume / 100;
} catch (...) { }
Genode::log("Change SDL audio volume to ", volume * 100);
}
Genode::Signal_handler<Volume_config> _config_handler {
_env.ep(), *this, &Volume_config::_handle_config_update };
Volume_config(Genode::Env &env) : _env(env)
{
_config_rom.sigh(_config_handler);
_handle_config_update();
}
};
struct SDL_PrivateAudioData { struct SDL_PrivateAudioData {
Uint8 *mixbuf; Uint8 *mixbuf;
Uint32 mixlen; Uint32 mixlen;
Constructible<Volume_config> volume_config;
Constructible<Audio_out::Connection> audio[AUDIO_CHANNELS]; Constructible<Audio_out::Connection> audio[AUDIO_CHANNELS];
Audio_out::Packet *packet[AUDIO_CHANNELS]; Audio_out::Packet *packet[AUDIO_CHANNELS];
}; };
/*
* The first 'Signal_receiver' object in a process creates a signal receiver
* thread. Currently this must not happen before the main program has started
* or else the thread's stack area would get overmapped on Genode/Linux when
* the main program calls 'main_thread_bootstrap()' from '_main()'.
*/
static Signal_receiver *signal_receiver()
{
static Signal_receiver _signal_receiver;
return &_signal_receiver;
}
static void read_config(Genode::Signal_context_capability sigh =
Genode::Signal_context_capability())
{
/* read volume from config file */
try {
unsigned int config_volume;
Genode::Attached_rom_dataspace config("config");
if (sigh.valid()) config.sigh(sigh);
else config.update();
config.xml().sub_node("sdl_audio_volume")
.attribute("value").value(&config_volume);
volume = (float)config_volume / 100;
}
catch (Genode::Xml_node::Nonexistent_sub_node) { }
catch (Genode::Xml_node::Nonexistent_attribute) { }
}
/* Audio driver functions */ /* Audio driver functions */
static int GENODEAUD_OpenAudio(_THIS, SDL_AudioSpec *spec); static int GENODEAUD_OpenAudio(_THIS, SDL_AudioSpec *spec);
static void GENODEAUD_WaitAudio(_THIS); static void GENODEAUD_WaitAudio(_THIS);
@ -153,7 +167,7 @@ static SDL_AudioDevice *GENODEAUD_CreateDevice(int devindex)
/* connect to 'Audio_out' service */ /* connect to 'Audio_out' service */
for (int channel = 0; channel < AUDIO_CHANNELS; channel++) { for (int channel = 0; channel < AUDIO_CHANNELS; channel++) {
try { try {
_this->hidden->audio[channel].construct( _this->hidden->audio[channel].construct(*global_env(),
channel_names[channel], false, channel == 0 ? true : false); channel_names[channel], false, channel == 0 ? true : false);
_this->hidden->audio[channel]->start(); _this->hidden->audio[channel]->start();
} }
@ -167,7 +181,7 @@ static SDL_AudioDevice *GENODEAUD_CreateDevice(int devindex)
} }
} }
read_config(signal_receiver()->manage(&config_signal_context)); _this->hidden->volume_config.construct(*global_env());
return _this; return _this;
} }
@ -200,6 +214,8 @@ static void GENODEAUD_WaitAudio(_THIS)
static void GENODEAUD_PlayAudio(_THIS) static void GENODEAUD_PlayAudio(_THIS)
{ {
Genode::Lock_guard<Genode::Lock> guard(event_lock);
Audio_out::Connection *c[AUDIO_CHANNELS]; Audio_out::Connection *c[AUDIO_CHANNELS];
Audio_out::Packet *p[AUDIO_CHANNELS]; Audio_out::Packet *p[AUDIO_CHANNELS];
for (int channel = 0; channel < AUDIO_CHANNELS; channel++) { for (int channel = 0; channel < AUDIO_CHANNELS; channel++) {
@ -214,6 +230,8 @@ static void GENODEAUD_PlayAudio(_THIS)
init = true; init = true;
} }
float const volume = _this->hidden->volume_config->volume;
/* /*
* Get new packet for left channel and use it to synchronize * Get new packet for left channel and use it to synchronize
* the right channel * the right channel
@ -222,11 +240,6 @@ static void GENODEAUD_PlayAudio(_THIS)
unsigned ppos = c[0]->stream()->packet_position(p[0]); unsigned ppos = c[0]->stream()->packet_position(p[0]);
p[1] = c[1]->stream()->get(ppos); p[1] = c[1]->stream()->get(ppos);
if (signal_receiver()->pending()) {
signal_receiver()->wait_for_signal();
read_config();
}
for (int sample = 0; sample < Audio_out::PERIOD; sample++) for (int sample = 0; sample < Audio_out::PERIOD; sample++)
for (int channel = 0; channel < AUDIO_CHANNELS; channel++) for (int channel = 0; channel < AUDIO_CHANNELS; channel++)
p[channel]->content()[sample] = p[channel]->content()[sample] =

View File

@ -0,0 +1,132 @@
/*
* \brief Entry point for SDL applications with a main() function
* \author Josef Soentgen
* \date 2017-11-21
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/sleep.h>
#include <libc/component.h>
/* libc includes */
#include <stdlib.h> /* 'malloc' and 'exit' */
#include <pthread.h>
extern char **genode_argv;
extern int genode_argc;
extern char **genode_envp;
/* initial environment for the FreeBSD libc implementation */
extern char **environ;
/* provided by the application */
extern "C" int main(int argc, char *argv[], char *envp[]);
/* provided by our SDL backend */
extern void sdl_init_genode(Genode::Env &env);
static void* sdl_main(void *)
{
exit(main(genode_argc, genode_argv, genode_envp));
return nullptr;
}
void Libc::Component::construct(Libc::Env &env)
{
using Genode::Xml_node;
using Genode::Xml_attribute;
env.config([&] (Xml_node const &node) {
int argc = 0;
int envc = 0;
char **argv;
char **envp;
/* count the number of arguments and environment variables */
node.for_each_sub_node([&] (Xml_node const &node) {
/* check if the 'value' attribute exists */
if (node.has_type("arg") && node.has_attribute("value"))
++argc;
else
if (node.has_type("env") && node.has_attribute("key") && node.has_attribute("value"))
++envc;
});
if (argc == 0 && envc == 0)
return; /* from lambda */
/* arguments and environment are a contiguous array (but don't count on it) */
argv = (char**)malloc((argc + envc + 1) * sizeof(char*));
envp = &argv[argc];
/* read the arguments */
int arg_i = 0;
int env_i = 0;
node.for_each_sub_node([&] (Xml_node const &node) {
/* insert an argument */
if (node.has_type("arg")) try {
Xml_attribute attr = node.attribute("value");
Genode::size_t const arg_len = attr.value_size()+1;
char *arg = argv[arg_i] = (char*)malloc(arg_len);
attr.value(arg, arg_len);
++arg_i;
} catch (Xml_node::Nonexistent_sub_node) { }
else
/* insert an environment variable */
if (node.has_type("env")) try {
Xml_attribute key_attr = node.attribute("key");
Xml_attribute val_attr = node.attribute("value");
Genode::size_t const pair_len =
key_attr.value_size() +
val_attr.value_size() + 1;
char *env = envp[env_i] = (char*)malloc(pair_len);
Genode::size_t off = 0;
key_attr.value(&env[off], key_attr.value_size()+1);
off = key_attr.value_size();
env[off++] = '=';
val_attr.value(&env[off], val_attr.value_size()+1);
++env_i;
} catch (Xml_node::Nonexistent_sub_node) { }
});
envp[env_i] = NULL;
/* register command-line arguments at Genode's startup code */
genode_argc = argc;
genode_argv = argv;
genode_envp = environ = envp;
});
/* pass env to SDL backend */
sdl_init_genode(env);
pthread_attr_t attr;
pthread_t main_thread;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 768 * 1024);
if (pthread_create(&main_thread, &attr, sdl_main, nullptr)) {
Genode::error("failed to create SDL main thread");
exit(1);
}
}

View File

@ -28,11 +28,41 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
/* Genode includes */
#include <base/log.h> #include <base/log.h>
#include <input_session/connection.h> #include <input_session/connection.h>
#include <input/event.h> #include <input/event.h>
#include <input/keycodes.h> #include <input/keycodes.h>
/* local includes */
#include <SDL_genode_internal.h>
Genode::Lock event_lock;
Video video_events;
static Genode::Env *_global_env = nullptr;
Genode::Env *global_env()
{
if (!_global_env) {
Genode::error("sdl_init_genode() not called, aborting");
throw Genode::Exception();
}
return _global_env;
}
void sdl_init_genode(Genode::Env &env)
{
_global_env = &env;
}
extern "C" { extern "C" {
#include <SDL/SDL.h> #include <SDL/SDL.h>
@ -40,7 +70,8 @@ extern "C" {
#include "SDL_sysevents.h" #include "SDL_sysevents.h"
#include "SDL_genode_fb_events.h" #include "SDL_genode_fb_events.h"
static Input::Connection *input = 0;
static Genode::Constructible<Input::Connection> input;
static const int KEYNUM_MAX = 512; static const int KEYNUM_MAX = 512;
static SDLKey keymap[KEYNUM_MAX]; static SDLKey keymap[KEYNUM_MAX];
static int buttonmap[KEYNUM_MAX]; static int buttonmap[KEYNUM_MAX];
@ -58,8 +89,17 @@ extern "C" {
void Genode_Fb_PumpEvents(SDL_VideoDevice *t) void Genode_Fb_PumpEvents(SDL_VideoDevice *t)
{ {
Genode::Lock_guard<Genode::Lock> guard(event_lock);
if (video_events.resize_pending) {
video_events.resize_pending = false;
SDL_PrivateResize(video_events.width, video_events.height);
}
if (!input->pending()) if (!input->pending())
return; return;
input->for_each_event([&] (Input::Event const &curr) { input->for_each_event([&] (Input::Event const &curr) {
SDL_keysym ksym; SDL_keysym ksym;
switch(curr.type()) switch(curr.type())
@ -104,14 +144,15 @@ extern "C" {
void Genode_Fb_InitOSKeymap(SDL_VideoDevice *t) void Genode_Fb_InitOSKeymap(SDL_VideoDevice *t)
{ {
using namespace Input; try {
input = new(Genode::env()->heap()) Connection(); input.construct(*_global_env);
if(!input->cap().valid()) } catch (...) {
{
Genode::error("no input driver available!"); Genode::error("no input driver available!");
return; return;
} }
using namespace Input;
/* Prepare button mappings */ /* Prepare button mappings */
for (int i=0; i<KEYNUM_MAX; i++) for (int i=0; i<KEYNUM_MAX; i++)
{ {

View File

@ -34,6 +34,16 @@
#include <base/env.h> #include <base/env.h>
#include <framebuffer_session/connection.h> #include <framebuffer_session/connection.h>
/* local includes */
#include <SDL_genode_internal.h>
extern Genode::Env *global_env();
extern Genode::Lock event_lock;
extern Video video_events;
extern "C" { extern "C" {
#include <dlfcn.h> #include <dlfcn.h>
@ -47,10 +57,52 @@ extern "C" {
#include "SDL_genode_fb_events.h" #include "SDL_genode_fb_events.h"
#include "SDL_genode_fb_video.h" #include "SDL_genode_fb_video.h"
static Framebuffer::Connection *framebuffer = 0; static SDL_Rect df_mode;
struct Sdl_framebuffer
{
Genode::Env &_env;
Framebuffer::Mode _mode;
Framebuffer::Connection _fb { _env, _mode };
void _handle_mode_change()
{
Genode::Lock_guard<Genode::Lock> guard(event_lock);
Framebuffer::Mode mode = _fb.mode();
df_mode.w = mode.width();
df_mode.h = mode.height();
video_events.resize_pending = true;
video_events.width = mode.width();
video_events.height = mode.height();
}
Genode::Signal_handler<Sdl_framebuffer> _mode_handler {
_env.ep(), *this, &Sdl_framebuffer::_handle_mode_change };
Sdl_framebuffer(Genode::Env &env) : _env(env) {
_fb.mode_sigh(_mode_handler); }
bool valid() const { return _fb.cap().valid(); }
/************************************
** Framebuffer::Session Interface **
************************************/
Genode::Dataspace_capability dataspace() { return _fb.dataspace(); }
Framebuffer::Mode mode() const { return _fb.mode(); }
void refresh(int x, int y, int w, int h) {
_fb.refresh(x, y, w, h); }
};
static Genode::Constructible<Sdl_framebuffer> framebuffer;
static Framebuffer::Mode scr_mode; static Framebuffer::Mode scr_mode;
static SDL_Rect *modes[2]; static SDL_Rect *modes[2];
static SDL_Rect df_mode;
#if defined(SDL_VIDEO_OPENGL) #if defined(SDL_VIDEO_OPENGL)
@ -216,11 +268,11 @@ extern "C" {
static int Genode_Fb_Available(void) static int Genode_Fb_Available(void)
{ {
if (framebuffer == nullptr) { if (!framebuffer.constructed()) {
framebuffer = new (Genode::env()->heap()) Framebuffer::Connection(); framebuffer.construct(*global_env());
} }
if (!framebuffer->cap().valid()) { if (!framebuffer->valid()) {
Genode::error("could not obtain framebuffer session"); Genode::error("could not obtain framebuffer session");
return 0; return 0;
} }
@ -231,12 +283,9 @@ extern "C" {
static void Genode_Fb_DeleteDevice(SDL_VideoDevice *device) static void Genode_Fb_DeleteDevice(SDL_VideoDevice *device)
{ {
Genode::log("free framebuffer session object"); if (framebuffer.constructed()) {
if(framebuffer != nullptr) { framebuffer.destruct();
Genode::destroy(Genode::env()->heap(), framebuffer);
} }
framebuffer = nullptr;
} }
@ -310,9 +359,8 @@ extern "C" {
*/ */
int Genode_Fb_VideoInit(SDL_VideoDevice *t, SDL_PixelFormat *vformat) int Genode_Fb_VideoInit(SDL_VideoDevice *t, SDL_PixelFormat *vformat)
{ {
if (framebuffer == 0) if (!framebuffer.constructed()) {
{ Genode::error("framebuffer not initialized");
Genode::error("framebuffer isn't initialized");
return -1; return -1;
} }
@ -345,17 +393,30 @@ extern "C" {
df_mode.h = scr_mode.height(); df_mode.h = scr_mode.height();
modes[1] = 0; modes[1] = 0;
/* Map the buffer */ t->hidden->buffer = 0;
Genode::Dataspace_capability fb_ds_cap = framebuffer->dataspace();
if (!fb_ds_cap.valid()) {
Genode::error("could not request dataspace for frame buffer");
return -1;
}
t->hidden->buffer = Genode::env()->rm_session()->attach(fb_ds_cap);
return 0; return 0;
} }
/**
*Note: If we are terminated, this could be called in the middle of
* another SDL video routine -- notably UpdateRects.
*/
void Genode_Fb_VideoQuit(SDL_VideoDevice *t)
{
Genode::log("Quit video device ...");
if (t->screen->pixels) {
t->screen->pixels = nullptr;
}
if (t->hidden->buffer) {
global_env()->rm().detach(t->hidden->buffer);
t->hidden->buffer = nullptr;
}
}
/** /**
* List the available video modes for the given pixel format, * List the available video modes for the given pixel format,
* sorted from largest to smallest. * sorted from largest to smallest.
@ -383,13 +444,32 @@ extern "C" {
int width, int height, int width, int height,
int bpp, Uint32 flags) int bpp, Uint32 flags)
{ {
Genode::log("Set video mode to: " /* for now we do not support this */
"width=", width, " " "height=", height, " " "bpp=", bpp); if (t->hidden->buffer && flags & SDL_OPENGL) {
Genode::error("resizing a OpenGL window not possible");
return nullptr;
}
/* Map the buffer */
Genode::Dataspace_capability fb_ds_cap = framebuffer->dataspace();
if (!fb_ds_cap.valid()) {
Genode::error("could not request dataspace for frame buffer");
return nullptr;
}
if (t->hidden->buffer) {
global_env()->rm().detach(t->hidden->buffer);
}
t->hidden->buffer = global_env()->rm().attach(fb_ds_cap);
if (!t->hidden->buffer) { if (!t->hidden->buffer) {
Genode::error("no buffer for requested mode"); Genode::error("no buffer for requested mode");
return nullptr; return nullptr;
} }
Genode::log("Set video mode to: ", width, "x", height, "@", bpp);
SDL_memset(t->hidden->buffer, 0, width * height * (bpp / 8)); SDL_memset(t->hidden->buffer, 0, width * height * (bpp / 8));
if (!SDL_ReallocFormat(current, bpp, 0, 0, 0, 0) ) { if (!SDL_ReallocFormat(current, bpp, 0, 0, 0, 0) ) {
@ -481,21 +561,6 @@ extern "C" {
} }
/**
*Note: If we are terminated, this could be called in the middle of
* another SDL video routine -- notably UpdateRects.
*/
void Genode_Fb_VideoQuit(SDL_VideoDevice *t)
{
Genode::log("Quit video device ...");
if (t->screen->pixels != 0)
{
SDL_free(t->screen->pixels);
t->screen->pixels = 0;
}
}
int Genode_Fb_GL_MakeCurrent(SDL_VideoDevice *t) int Genode_Fb_GL_MakeCurrent(SDL_VideoDevice *t)
{ {
Genode::warning(__func__, ": not yet implemented"); Genode::warning(__func__, ": not yet implemented");
@ -523,5 +588,4 @@ extern "C" {
{ {
return !__mesa ? nullptr : dlsym(__mesa, proc); return !__mesa ? nullptr : dlsym(__mesa, proc);
} }
} //extern "C" } //extern "C"

View File

@ -1,3 +1,3 @@
TARGET = test-sdl TARGET := test-sdl
LIBS = sdl posix LIBS := libc sdl sdlmain
SRC_CC = main.cc SRC_CC := main.cc

View File

@ -88,7 +88,7 @@ append config {
<resource name="RAM" quantum="1M"/> <resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides> <provides><service name="Timer"/></provides>
</start> </start>
<start name="dosbox"> <start name="dosbox" caps="200">
<resource name="RAM" quantum="128M"/> <resource name="RAM" quantum="128M"/>
<config> <config>
<sdl_audio_volume value="100"/> <sdl_audio_volume value="100"/>
@ -121,7 +121,7 @@ if {![file exists bin/dosbox.tar]} {
append boot_modules { append boot_modules {
core init timer } [audio_drv_binary] { core init timer } [audio_drv_binary] {
ld.lib.so ld.lib.so
libc.lib.so posix.lib.so libc.lib.so
libm.lib.so lwip.lib.so libpng.lib.so libm.lib.so lwip.lib.so libpng.lib.so
stdcxx.lib.so sdl.lib.so sdl_net.lib.so pthread.lib.so zlib.lib.so stdcxx.lib.so sdl.lib.so sdl_net.lib.so pthread.lib.so zlib.lib.so
dosbox dosbox.tar dosbox dosbox.tar

View File

@ -55,5 +55,5 @@ CC_WARN += -Wno-unused-variable -Wno-unused-function -Wno-switch -Wno-unused-val
-Wno-sign-compare -Wno-narrowing -Wno-missing-braces -Wno-array-bounds \ -Wno-sign-compare -Wno-narrowing -Wno-missing-braces -Wno-array-bounds \
-Wno-parentheses -Wno-parentheses
LIBS += posix libpng sdl sdl_net stdcxx zlib LIBS += libpng libc sdl sdlmain sdl_net stdcxx zlib
LIBS += libc_lwip_nic_dhcp LIBS += libc_lwip_nic_dhcp