import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as T
import qualified Data.Text.Lazy.Read as T
import qualified Data.Vector.Unboxed as VU
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS

main :: IO ()
main = do
  [h, w] <- map unsafeTextToInt . T.words <$> T.getLine :: IO [Int]
  let
    encode :: (Int, Int) -> Int
    encode (i, j) = i * w + j
    decode :: Int -> (Int, Int)
    decode = (`divMod` w)
    inside :: (Int, Int) -> Bool
    inside (i, j) = 0 <= i && i < h && 0 <= j && j < w
    neighbors :: VU.Vector Bool -> Int -> [Int]
    neighbors v k | v VU.! k  = filter (v VU.!) . map encode . filter inside $ [(i, j - 1), (i, j + 1), (i - 1, j), (i + 1, j)]
                  | otherwise = []
      where
        (i, j) = decode k
    search :: VU.Vector Bool -> Int -> Int
    search v k0 = snd . last $ queue
      where
        queue = (k0, 0) : walk 1 IS.empty queue
        walk :: Int -> IntSet -> [(Int, Int)] -> [(Int, Int)]
        walk 0 _ _ = []
        walk l u ((k, d) : q) = map (flip (,) (d + 1)) ns ++ walk (l - 1 + length ns) u' q
          where
            ns = filter (`IS.notMember` u) $ neighbors v k
            u' = foldl (flip IS.insert) u ns
  bs <- VU.fromList . map ('.' ==) . concat . lines <$> getContents :: IO (VU.Vector Bool)
  print . VU.maximum . VU.map (search bs) $ VU.enumFromN 0 (h * w)

unsafeTextToInt :: T.Text -> Int
unsafeTextToInt s = case T.signed T.decimal s of
  Right (n, _) -> n
{-# INLINE unsafeTextToInt #-}
