There are multiple things wrong with you're code.
Starting from the bottom up, first, length [l1] doesn't make sense. Any [ ] with only one item in between is just that: a list with a single item, so the length will always be 1. You certainly mean length l1 here, i.e. length of the list l1, not length of the list ᴄᴏɴᴛᴀɪɴɪɴɢ only l1.
Next, you have this iotxt and try to make it modify the "global variable" l1. You can't do that, Haskell does not have any such thing as mutable global variables – for good reasons; global mutable state is considered evil even in imperative languages. Haskell kind of has local variables, through IORefs, but using those without good reason is frowned upon. You don't really need them for something like this here.
The correct thing to do is to scrap this global l1 binding, forget about mutating variables. Which brings us to the question of how to pass on the information acquired in iotxt. Well, the obvious functional thing to do is, returning it. We need to make that explicit in the type (which is again a good thing, so we actually know how to use the function):
ioTxt :: IO [Int]
Such a function can then nicely be used in main:
main :: IO ()
main = do
putStrLn "Enter a number [-1 to quit]"
l1 <- ioTxt
print $ length l1
You see: almost the same as your approach, but with proper explicit introduction of l1 where you need it, rather than somewhere completely different.
What's left to do is implementing ioTxt. This now also needs a local l1 variable since we have scrapped the global one. And when you implement a loop as such a recursive call, you need to pass an updated version of it to each instantiation. The recursion itself should be done on a locally-defined function.
ioTxt :: IO [Int]
ioTxT = go [] -- Start with empty list. `go` is a widespread name for such simple "loop functions".
where go l1 = do
a <- getLine
let x = read a :: Int
case x of
(-1) -> return l1
_ -> go (insert x l1)
In the last line, note that insert does not modify the list l1, it rather creates a new one that equals the old one except for having the new element in it. And then passes that to the next loop-call, so effectively you get an updated list for each recursive call.
Also note that you probably shouldn't use insert here: that's specifically for placing the new element at the right position in an ordered list. If you just want to accumulate the values in some way, you can simply use
_ -> go $ x : l1